Repository: open-telemetry/opentelemetry-collector Branch: main Commit: fdadab8a8302 Files: 2607 Total size: 9.5 MB Directory structure: gitextract_tdgbeses/ ├── .checkapi.yaml ├── .chloggen/ │ ├── README.md │ ├── TEMPLATE.yaml │ ├── aix_tier3.yaml │ ├── alpha-profiles.yaml │ ├── config.yaml │ ├── fix-mdatagen-entity-builder.yaml │ ├── mdatagen_fix_reporoot.yaml │ ├── remove-resource-constant-labels.yaml │ └── summary.tmpl ├── .codecov.yml ├── .gitattributes ├── .github/ │ ├── ALLOWLIST │ ├── CODEOWNERS │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.yaml │ │ ├── component-graduation.md │ │ ├── feature_request.yaml │ │ ├── other.yaml │ │ ├── stabilization.md │ │ └── vote.md │ ├── actionlint.yaml │ ├── lychee.toml │ ├── pull_request_template.md │ └── workflows/ │ ├── add-labels-and-owners.yml │ ├── add-labels-command.yml │ ├── api-compatibility.yml │ ├── build-and-test-arm.yml │ ├── build-and-test-windows.yaml │ ├── build-and-test.yml │ ├── builder-integration-test.yaml │ ├── builder-snapshot.yaml │ ├── changelog.yml │ ├── check-codeowners.yaml │ ├── check-links.yaml │ ├── check-merge-freeze.yml │ ├── codeql-analysis.yml │ ├── contrib-tests.yml │ ├── fossa.yml │ ├── go-benchmarks.yml │ ├── lint-workflow-files.yml │ ├── milestone-add-to-pr.yml │ ├── perf.yml │ ├── ping-codeowners-issues.yml │ ├── ping-codeowners-on-new-issue.yml │ ├── ping-codeowners-prs.yml │ ├── prepare-release.yml │ ├── release-branch.yml │ ├── rerun-workflows.yml │ ├── scorecard.yml │ ├── scripts/ │ │ ├── add-labels-and-owners.sh │ │ ├── add-labels-command.sh │ │ ├── check-merge-freeze.sh │ │ ├── free-disk-space.sh │ │ ├── get-codeowners.sh │ │ ├── get-components.sh │ │ ├── ping-codeowners-issues.sh │ │ ├── ping-codeowners-on-new-issue.sh │ │ ├── ping-codeowners-prs.sh │ │ ├── release-branch.sh │ │ ├── release-check-blockers.sh │ │ ├── release-check-build-status.sh │ │ ├── release-create-tracking-issue.sh │ │ ├── release-prepare-release.sh │ │ ├── rerun-failed-workflows.sh │ │ └── win-required-ports.ps1 │ ├── shellcheck.yml │ ├── sourcecode-release.yaml │ ├── spell-check.yaml │ ├── stale-pr.yaml │ ├── survey-on-merged-pr.yml │ ├── tidy-dependencies.yml │ └── utils/ │ └── cspell.json ├── .gitignore ├── .golangci.yml ├── .markdownlint.yaml ├── .markdownlintignore ├── AGENTS.md ├── CHANGELOG-API.md ├── CHANGELOG.md ├── CLAUDE.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── Makefile.Common ├── README.md ├── VERSIONING.md ├── client/ │ ├── Makefile │ ├── client.go │ ├── client_test.go │ ├── doc_test.go │ ├── go.mod │ ├── go.sum │ ├── metadata.yaml │ └── package_test.go ├── cmd/ │ ├── builder/ │ │ ├── Makefile │ │ ├── README.md │ │ ├── RELEASE.md │ │ ├── go.mod │ │ ├── go.sum │ │ ├── header.txt │ │ ├── internal/ │ │ │ ├── .gitignore │ │ │ ├── builder/ │ │ │ │ ├── config.go │ │ │ │ ├── config_test.go │ │ │ │ ├── main.go │ │ │ │ ├── main_test.go │ │ │ │ ├── package_test.go │ │ │ │ ├── templates/ │ │ │ │ │ ├── components.go.tmpl │ │ │ │ │ ├── go.mod.tmpl │ │ │ │ │ ├── main.go.tmpl │ │ │ │ │ ├── main_others.go.tmpl │ │ │ │ │ └── main_windows.go.tmpl │ │ │ │ └── templates.go │ │ │ ├── command.go │ │ │ ├── command_init.go │ │ │ ├── command_init_test.go │ │ │ ├── command_test.go │ │ │ ├── config/ │ │ │ │ ├── default.go │ │ │ │ └── default.yaml │ │ │ ├── init/ │ │ │ │ └── templates/ │ │ │ │ ├── .gitignore.tmpl │ │ │ │ ├── Makefile.tmpl │ │ │ │ ├── README.md.tmpl │ │ │ │ ├── config.yaml.tmpl │ │ │ │ ├── go.mod.tmpl │ │ │ │ └── manifest.yaml.tmpl │ │ │ ├── package_test.go │ │ │ └── version.go │ │ ├── main.go │ │ ├── metadata.yaml │ │ └── test/ │ │ ├── README.md │ │ ├── core.builder.yaml │ │ ├── core.otel.yaml │ │ └── test.sh │ ├── githubgen/ │ │ └── allowlist.txt │ ├── mdatagen/ │ │ ├── Makefile │ │ ├── README.md │ │ ├── generated_package_test.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── internal/ │ │ │ ├── cfggen/ │ │ │ │ ├── generation.go │ │ │ │ ├── generation_test.go │ │ │ │ ├── loader.go │ │ │ │ ├── loader_test.go │ │ │ │ ├── model.go │ │ │ │ ├── model_test.go │ │ │ │ ├── namespace.go │ │ │ │ ├── namespace_test.go │ │ │ │ ├── resolver.go │ │ │ │ ├── resolver_test.go │ │ │ │ ├── type_ref.go │ │ │ │ ├── type_ref_test.go │ │ │ │ ├── writer.go │ │ │ │ └── writer_test.go │ │ │ ├── command.go │ │ │ ├── command_test.go │ │ │ ├── embedded_templates.go │ │ │ ├── embedded_templates_test.go │ │ │ ├── event.go │ │ │ ├── event_test.go │ │ │ ├── helpers/ │ │ │ │ ├── lint.go │ │ │ │ ├── lint_test.go │ │ │ │ ├── packages.go │ │ │ │ └── packages_test.go │ │ │ ├── loader.go │ │ │ ├── loader_test.go │ │ │ ├── metadata.go │ │ │ ├── metadata_test.go │ │ │ ├── metric.go │ │ │ ├── metric_test.go │ │ │ ├── sampleconnector/ │ │ │ │ ├── doc.go │ │ │ │ ├── documentation.md │ │ │ │ ├── factory.go │ │ │ │ ├── generated_component_test.go │ │ │ │ ├── generated_package_test.go │ │ │ │ ├── internal/ │ │ │ │ │ └── metadata/ │ │ │ │ │ ├── config.schema.yaml │ │ │ │ │ ├── generated_config.go │ │ │ │ │ ├── generated_config_test.go │ │ │ │ │ ├── generated_entity_metrics.go │ │ │ │ │ ├── generated_entity_metrics_test.go │ │ │ │ │ ├── generated_metrics.go │ │ │ │ │ ├── generated_metrics_test.go │ │ │ │ │ ├── generated_resource.go │ │ │ │ │ ├── generated_resource_test.go │ │ │ │ │ ├── generated_status.go │ │ │ │ │ └── testdata/ │ │ │ │ │ └── config.yaml │ │ │ │ ├── metadata.yaml │ │ │ │ └── metrics_test.go │ │ │ ├── sampleentityreceiver/ │ │ │ │ ├── doc.go │ │ │ │ ├── documentation.md │ │ │ │ ├── factory.go │ │ │ │ ├── generated_component_test.go │ │ │ │ ├── generated_package_test.go │ │ │ │ ├── internal/ │ │ │ │ │ └── metadata/ │ │ │ │ │ ├── config.schema.yaml │ │ │ │ │ ├── generated_config.go │ │ │ │ │ ├── generated_config_test.go │ │ │ │ │ ├── generated_entity_metrics.go │ │ │ │ │ ├── generated_entity_metrics_test.go │ │ │ │ │ ├── generated_metrics.go │ │ │ │ │ ├── generated_metrics_test.go │ │ │ │ │ ├── generated_resource.go │ │ │ │ │ ├── generated_resource_test.go │ │ │ │ │ ├── generated_status.go │ │ │ │ │ └── testdata/ │ │ │ │ │ └── config.yaml │ │ │ │ └── metadata.yaml │ │ │ ├── samplefactoryreceiver/ │ │ │ │ ├── README.md │ │ │ │ ├── doc.go │ │ │ │ ├── documentation.md │ │ │ │ ├── factory.go │ │ │ │ ├── generated_component_test.go │ │ │ │ ├── generated_package_test.go │ │ │ │ ├── internal/ │ │ │ │ │ └── metadata/ │ │ │ │ │ ├── generated_config.go │ │ │ │ │ ├── generated_config_test.go │ │ │ │ │ ├── generated_logs.go │ │ │ │ │ ├── generated_logs_test.go │ │ │ │ │ ├── generated_metrics.go │ │ │ │ │ ├── generated_metrics_test.go │ │ │ │ │ ├── generated_resource.go │ │ │ │ │ ├── generated_resource_test.go │ │ │ │ │ ├── generated_status.go │ │ │ │ │ ├── generated_telemetry.go │ │ │ │ │ ├── generated_telemetry_test.go │ │ │ │ │ └── testdata/ │ │ │ │ │ └── config.yaml │ │ │ │ └── metadata.yaml │ │ │ ├── sampleprocessor/ │ │ │ │ ├── README.md │ │ │ │ ├── doc.go │ │ │ │ ├── documentation.md │ │ │ │ ├── factory.go │ │ │ │ ├── generated_component_test.go │ │ │ │ ├── generated_package_test.go │ │ │ │ ├── internal/ │ │ │ │ │ └── metadata/ │ │ │ │ │ ├── config.schema.yaml │ │ │ │ │ ├── generated_config.go │ │ │ │ │ ├── generated_config_test.go │ │ │ │ │ ├── generated_resource.go │ │ │ │ │ ├── generated_resource_test.go │ │ │ │ │ ├── generated_status.go │ │ │ │ │ └── testdata/ │ │ │ │ │ └── config.yaml │ │ │ │ └── metadata.yaml │ │ │ ├── samplereceiver/ │ │ │ │ ├── README.md │ │ │ │ ├── config.schema.json │ │ │ │ ├── doc.go │ │ │ │ ├── documentation.md │ │ │ │ ├── factory.go │ │ │ │ ├── generated_component_test.go │ │ │ │ ├── generated_config.go │ │ │ │ ├── generated_package_test.go │ │ │ │ ├── internal/ │ │ │ │ │ ├── metadata/ │ │ │ │ │ │ ├── config.schema.yaml │ │ │ │ │ │ ├── generated_config.go │ │ │ │ │ │ ├── generated_config_test.go │ │ │ │ │ │ ├── generated_feature_gates.go │ │ │ │ │ │ ├── generated_logs.go │ │ │ │ │ │ ├── generated_logs_test.go │ │ │ │ │ │ ├── generated_metrics.go │ │ │ │ │ │ ├── generated_metrics_test.go │ │ │ │ │ │ ├── generated_resource.go │ │ │ │ │ │ ├── generated_resource_test.go │ │ │ │ │ │ ├── generated_status.go │ │ │ │ │ │ ├── generated_telemetry.go │ │ │ │ │ │ ├── generated_telemetry_test.go │ │ │ │ │ │ └── testdata/ │ │ │ │ │ │ └── config.yaml │ │ │ │ │ └── metadatatest/ │ │ │ │ │ ├── generated_telemetrytest.go │ │ │ │ │ └── generated_telemetrytest_test.go │ │ │ │ ├── metadata.yaml │ │ │ │ └── metrics_test.go │ │ │ ├── samplescraper/ │ │ │ │ ├── README.md │ │ │ │ ├── config.schema.json │ │ │ │ ├── doc.go │ │ │ │ ├── documentation.md │ │ │ │ ├── factory.go │ │ │ │ ├── generated_component_test.go │ │ │ │ ├── generated_config.go │ │ │ │ ├── generated_package_test.go │ │ │ │ ├── internal/ │ │ │ │ │ └── metadata/ │ │ │ │ │ ├── config.schema.yaml │ │ │ │ │ ├── generated_config.go │ │ │ │ │ ├── generated_config_test.go │ │ │ │ │ ├── generated_logs.go │ │ │ │ │ ├── generated_logs_test.go │ │ │ │ │ ├── generated_metrics.go │ │ │ │ │ ├── generated_metrics_test.go │ │ │ │ │ ├── generated_resource.go │ │ │ │ │ ├── generated_resource_test.go │ │ │ │ │ ├── generated_status.go │ │ │ │ │ └── testdata/ │ │ │ │ │ └── config.yaml │ │ │ │ └── metadata.yaml │ │ │ ├── status.go │ │ │ ├── status_test.go │ │ │ ├── telemetry.go │ │ │ ├── templates/ │ │ │ │ ├── component_test.go.tmpl │ │ │ │ ├── config.go.tmpl │ │ │ │ ├── config.schema.yaml.tmpl │ │ │ │ ├── config_from_cfggen.go.tmpl │ │ │ │ ├── config_test.go.tmpl │ │ │ │ ├── documentation.md.tmpl │ │ │ │ ├── entity_metrics.go.tmpl │ │ │ │ ├── entity_metrics_test.go.tmpl │ │ │ │ ├── feature_gates.go.tmpl │ │ │ │ ├── feature_gates.md.tmpl │ │ │ │ ├── helper.tmpl │ │ │ │ ├── logs.go.tmpl │ │ │ │ ├── logs_test.go.tmpl │ │ │ │ ├── metrics.go.tmpl │ │ │ │ ├── metrics_test.go.tmpl │ │ │ │ ├── package_test.go.tmpl │ │ │ │ ├── readme.md.tmpl │ │ │ │ ├── resource.go.tmpl │ │ │ │ ├── resource_test.go.tmpl │ │ │ │ ├── status.go.tmpl │ │ │ │ ├── telemetry.go.tmpl │ │ │ │ ├── telemetry_test.go.tmpl │ │ │ │ ├── telemetrytest.go.tmpl │ │ │ │ ├── telemetrytest_test.go.tmpl │ │ │ │ └── testdata/ │ │ │ │ └── config.yaml.tmpl │ │ │ ├── testdata/ │ │ │ │ ├── async_metric.yaml │ │ │ │ ├── basic_connector.yaml │ │ │ │ ├── basic_pkg.yaml │ │ │ │ ├── basic_receiver.yaml │ │ │ │ ├── custom_generated_package_name.yaml │ │ │ │ ├── deprecation_info_invalid_date.yaml │ │ │ │ ├── display_name.yaml │ │ │ │ ├── documentation.md │ │ │ │ ├── empty.go │ │ │ │ ├── empty_test_config.yaml │ │ │ │ ├── entity_duplicate_attributes.yaml │ │ │ │ ├── entity_duplicate_types.yaml │ │ │ │ ├── entity_empty_id_attributes.yaml │ │ │ │ ├── entity_event_missing_association.yaml │ │ │ │ ├── entity_metric_missing_association.yaml │ │ │ │ ├── entity_metrics_events_valid.yaml │ │ │ │ ├── entity_relationships_bidirectional.yaml │ │ │ │ ├── entity_relationships_empty_target.yaml │ │ │ │ ├── entity_relationships_empty_type.yaml │ │ │ │ ├── entity_relationships_undefined_target.yaml │ │ │ │ ├── entity_relationships_valid.yaml │ │ │ │ ├── entity_single_metric_missing_association.yaml │ │ │ │ ├── entity_undefined_description_attribute.yaml │ │ │ │ ├── entity_undefined_id_attribute.yaml │ │ │ │ ├── entity_undefined_reference.yaml │ │ │ │ ├── entity_valid.yaml │ │ │ │ ├── events/ │ │ │ │ │ ├── basic_event.yaml │ │ │ │ │ ├── empty.go │ │ │ │ │ ├── no_description.yaml │ │ │ │ │ ├── no_enabled.yaml │ │ │ │ │ └── unknown_attribute.yaml │ │ │ │ ├── feature_gates.yaml │ │ │ │ ├── generated_component_test.go │ │ │ │ ├── generated_package_name.yaml │ │ │ │ ├── generated_package_test.go │ │ │ │ ├── internal/ │ │ │ │ │ └── metadata/ │ │ │ │ │ └── generated_status.go │ │ │ │ ├── invalid.yaml │ │ │ │ ├── invalid_aggregation.yaml │ │ │ │ ├── invalid_class.yaml │ │ │ │ ├── invalid_config.yaml │ │ │ │ ├── invalid_entity_stability.yaml │ │ │ │ ├── invalid_input_type.yaml │ │ │ │ ├── invalid_metric_semconvref.yaml │ │ │ │ ├── invalid_metric_stability.yaml │ │ │ │ ├── invalid_stability.yaml │ │ │ │ ├── invalid_stability_component.yaml │ │ │ │ ├── invalid_telemetry_missing_value_type_for_histogram.yaml │ │ │ │ ├── invalid_type_attr.yaml │ │ │ │ ├── invalid_type_rattr.yaml │ │ │ │ ├── metrics_and_type.yaml │ │ │ │ ├── no_aggregation.yaml │ │ │ │ ├── no_class.yaml │ │ │ │ ├── no_deprecation_date_info.yaml │ │ │ │ ├── no_deprecation_info.yaml │ │ │ │ ├── no_deprecation_migration_info.yaml │ │ │ │ ├── no_description_attr.yaml │ │ │ │ ├── no_description_rattr.yaml │ │ │ │ ├── no_display_name.yaml │ │ │ │ ├── no_enabled.yaml │ │ │ │ ├── no_metric_description.yaml │ │ │ │ ├── no_metric_stability.yaml │ │ │ │ ├── no_metric_type.yaml │ │ │ │ ├── no_metric_unit.yaml │ │ │ │ ├── no_monotonic.yaml │ │ │ │ ├── no_stability.yaml │ │ │ │ ├── no_stability_component.yaml │ │ │ │ ├── no_status.yaml │ │ │ │ ├── no_type.yaml │ │ │ │ ├── no_type_attr.yaml │ │ │ │ ├── no_type_rattr.yaml │ │ │ │ ├── no_value_type.yaml │ │ │ │ ├── parent.yaml │ │ │ │ ├── readme_with_cmd_class.md │ │ │ │ ├── readme_with_multiple_signals.md │ │ │ │ ├── readme_with_multiple_signals_and_deprecation.md │ │ │ │ ├── readme_with_status.md │ │ │ │ ├── readme_with_status_codeowners.md │ │ │ │ ├── readme_with_status_codeowners_and_emeritus.md │ │ │ │ ├── readme_with_status_codeowners_and_seeking_new.md │ │ │ │ ├── readme_with_status_converter.md │ │ │ │ ├── readme_with_status_extension.md │ │ │ │ ├── readme_with_status_provider.md │ │ │ │ ├── readme_with_warnings.md │ │ │ │ ├── readme_without_status.md │ │ │ │ ├── resource_attributes_only.yaml │ │ │ │ ├── status_only.yaml │ │ │ │ ├── two_metric_types.yaml │ │ │ │ ├── twopackages.yaml │ │ │ │ ├── undeprecated_with_deprecation.yaml │ │ │ │ ├── unknown_metric_attribute.yaml │ │ │ │ ├── unknown_value_type.yaml │ │ │ │ ├── unsorted_rattr.yaml │ │ │ │ ├── unused_attribute.yaml │ │ │ │ ├── with_conditional_attribute.yaml │ │ │ │ ├── with_config.yaml │ │ │ │ ├── with_description.yaml │ │ │ │ ├── with_goleak_ignores.yaml │ │ │ │ ├── with_goleak_setup.yaml │ │ │ │ ├── with_goleak_skip.yaml │ │ │ │ ├── with_goleak_teardown.yaml │ │ │ │ ├── with_invalid_config_ref.yaml │ │ │ │ ├── with_stability_from.yaml │ │ │ │ ├── with_telemetry.yaml │ │ │ │ ├── with_tests_connector.yaml │ │ │ │ ├── with_tests_exporter.yaml │ │ │ │ ├── with_tests_extension.yaml │ │ │ │ ├── with_tests_processor.yaml │ │ │ │ ├── with_tests_profiles_connector.yaml │ │ │ │ ├── with_tests_receiver.yaml │ │ │ │ └── with_underscore_in_semconv_ref_anchor_tag.yaml │ │ │ └── tests.go │ │ ├── main.go │ │ ├── metadata-schema.yaml │ │ ├── metadata.yaml │ │ └── third_party/ │ │ └── golint/ │ │ ├── LICENSE │ │ └── golint.go │ └── otelcorecol/ │ ├── Makefile │ ├── README.md │ ├── builder-config.yaml │ ├── components.go │ ├── go.mod │ ├── go.sum │ ├── main.go │ ├── main_others.go │ └── main_windows.go ├── component/ │ ├── Makefile │ ├── build_info.go │ ├── component.go │ ├── component_test.go │ ├── componentstatus/ │ │ ├── Makefile │ │ ├── go.mod │ │ ├── go.sum │ │ ├── instance.go │ │ ├── instance_test.go │ │ ├── metadata.yaml │ │ ├── status.go │ │ └── status_test.go │ ├── componenttest/ │ │ ├── Makefile │ │ ├── configtest.go │ │ ├── configtest_test.go │ │ ├── doc.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── metadata.yaml │ │ ├── nop_host.go │ │ ├── nop_host_test.go │ │ ├── nop_telemetry.go │ │ ├── nop_telemetry_test.go │ │ ├── package_test.go │ │ ├── telemetry.go │ │ └── telemetry_test.go │ ├── config.go │ ├── doc.go │ ├── go.mod │ ├── go.sum │ ├── host.go │ ├── identifiable.go │ ├── identifiable_example_test.go │ ├── identifiable_test.go │ ├── metadata.yaml │ ├── package_test.go │ └── telemetry.go ├── config/ │ ├── configauth/ │ │ ├── Makefile │ │ ├── README.md │ │ ├── config.schema.yaml │ │ ├── configauth.go │ │ ├── configauth_test.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── metadata.yaml │ │ └── package_test.go │ ├── configcompression/ │ │ ├── Makefile │ │ ├── compressiontype.go │ │ ├── compressiontype_test.go │ │ ├── config.schema.yaml │ │ ├── go.mod │ │ ├── go.sum │ │ ├── metadata.yaml │ │ └── package_test.go │ ├── configgrpc/ │ │ ├── Makefile │ │ ├── README.md │ │ ├── client_middleware_test.go │ │ ├── config.schema.yaml │ │ ├── configgrpc.go │ │ ├── configgrpc_benchmark_test.go │ │ ├── configgrpc_test.go │ │ ├── doc.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── gzip.go │ │ ├── metadata.yaml │ │ ├── package_test.go │ │ ├── server_middleware_test.go │ │ ├── testdata/ │ │ │ ├── ca.crt │ │ │ ├── client.crt │ │ │ ├── client.key │ │ │ ├── server.crt │ │ │ └── server.key │ │ ├── wrappedstream.go │ │ └── wrappedstream_test.go │ ├── confighttp/ │ │ ├── Makefile │ │ ├── README.md │ │ ├── client.go │ │ ├── client_middleware_test.go │ │ ├── client_test.go │ │ ├── clientinfohandler.go │ │ ├── clientinfohandler_test.go │ │ ├── compress_readcloser.go │ │ ├── compress_readcloser_test.go │ │ ├── compression.go │ │ ├── compression_test.go │ │ ├── compressor.go │ │ ├── compressor_test.go │ │ ├── config.schema.yaml │ │ ├── confighttp_example_test.go │ │ ├── doc.go │ │ ├── documentation.md │ │ ├── generated_package_test.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── internal/ │ │ │ ├── metadata/ │ │ │ │ └── generated_feature_gates.go │ │ │ └── options.go │ │ ├── metadata.yaml │ │ ├── server.go │ │ ├── server_middleware_test.go │ │ ├── server_test.go │ │ ├── testdata/ │ │ │ ├── ca.crt │ │ │ ├── client.crt │ │ │ ├── client.key │ │ │ ├── config.yaml │ │ │ ├── middlewares.yaml │ │ │ ├── server.crt │ │ │ └── server.key │ │ └── xconfighttp/ │ │ ├── Makefile │ │ ├── go.mod │ │ ├── go.sum │ │ ├── metadata.yaml │ │ ├── options.go │ │ └── options_test.go │ ├── configmiddleware/ │ │ ├── Makefile │ │ ├── README.md │ │ ├── config.schema.yaml │ │ ├── configmiddleware.go │ │ ├── configmiddleware_test.go │ │ ├── go.mod │ │ ├── go.sum │ │ └── metadata.yaml │ ├── confignet/ │ │ ├── Makefile │ │ ├── README.md │ │ ├── config.schema.yaml │ │ ├── confignet.go │ │ ├── confignet_test.go │ │ ├── doc.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── metadata.yaml │ │ └── package_test.go │ ├── configopaque/ │ │ ├── Makefile │ │ ├── config.schema.yaml │ │ ├── doc.go │ │ ├── doc_test.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── maplist.go │ │ ├── maplist_test.go │ │ ├── metadata.yaml │ │ ├── opaque.go │ │ ├── opaque_test.go │ │ └── package_test.go │ ├── configoptional/ │ │ ├── Makefile │ │ ├── documentation.md │ │ ├── generated_package_test.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── internal/ │ │ │ └── metadata/ │ │ │ └── generated_feature_gates.go │ │ ├── metadata.yaml │ │ ├── optional.go │ │ ├── optional_test.go │ │ └── testdata/ │ │ ├── validate_explicit.yaml │ │ ├── validate_implicit.yaml │ │ ├── validate_invalid.yaml │ │ └── validate_no_default.yaml │ ├── configretry/ │ │ ├── Makefile │ │ ├── backoff.go │ │ ├── backoff_test.go │ │ ├── config.schema.yaml │ │ ├── go.mod │ │ ├── go.sum │ │ ├── metadata.yaml │ │ └── package_test.go │ ├── configtelemetry/ │ │ ├── Makefile │ │ ├── config.schema.yaml │ │ ├── configtelemetry.go │ │ ├── configtelemetry_test.go │ │ ├── doc.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── metadata.yaml │ │ └── package_test.go │ └── configtls/ │ ├── Makefile │ ├── README.md │ ├── clientcasfilereloader.go │ ├── clientcasfilereloader_test.go │ ├── config.schema.yaml │ ├── configtls.go │ ├── configtls_test.go │ ├── curves_fips.go │ ├── curves_nofips.go │ ├── doc.go │ ├── go.mod │ ├── go.sum │ ├── metadata.yaml │ ├── testdata/ │ │ ├── ca-1.crt │ │ ├── ca-2.crt │ │ ├── client-1.crt │ │ ├── client-1.key │ │ ├── client-2.crt │ │ ├── client-2.key │ │ ├── server-1.crt │ │ ├── server-1.key │ │ ├── server-2.crt │ │ ├── server-2.key │ │ └── testCA-bad.txt │ ├── tpm.go │ ├── tpm_open_linux.go │ ├── tpm_open_others.go │ ├── tpm_open_windows.go │ └── tpm_test.go ├── confmap/ │ ├── Makefile │ ├── README.md │ ├── confmap.go │ ├── confmaptest/ │ │ ├── configtest.go │ │ ├── configtest_test.go │ │ ├── doc.go │ │ ├── package_test.go │ │ ├── provider_settings.go │ │ └── testdata/ │ │ ├── empty-slice.yaml │ │ ├── invalid.yaml │ │ └── simple.yaml │ ├── converter.go │ ├── doc_test.go │ ├── documentation.md │ ├── example_provider_and_converter_test.go │ ├── expand.go │ ├── expand_test.go │ ├── factory.go │ ├── generated_package_test.go │ ├── go.mod │ ├── go.sum │ ├── internal/ │ │ ├── conf.go │ │ ├── confmap.go │ │ ├── confmap_test.go │ │ ├── decoder.go │ │ ├── e2e/ │ │ │ ├── Makefile │ │ │ ├── expand_test.go │ │ │ ├── fuzz_test.go │ │ │ ├── go.mod │ │ │ ├── go.sum │ │ │ ├── maplist_expanded_test.go │ │ │ ├── nil_test.go │ │ │ ├── testdata/ │ │ │ │ ├── expand-escaped-env.yaml │ │ │ │ ├── indirect-slice-env-var-main.yaml │ │ │ │ ├── indirect-slice-env-var-pipelines.yaml │ │ │ │ ├── issue-10787-main.yaml │ │ │ │ ├── issue-10787-snippet.yaml │ │ │ │ ├── subsection_empty_map.yaml │ │ │ │ ├── subsection_null.yaml │ │ │ │ ├── subsection_set_but_empty.yaml │ │ │ │ ├── subsection_unset.yaml │ │ │ │ ├── subsection_unset_empty_map.yaml │ │ │ │ ├── types_complex.yaml │ │ │ │ ├── types_expand.yaml │ │ │ │ ├── types_expand_inline.yaml │ │ │ │ ├── types_map.yaml │ │ │ │ └── types_slice.yaml │ │ │ └── types_test.go │ │ ├── encoder.go │ │ ├── envvar/ │ │ │ └── pattern.go │ │ ├── expand.go │ │ ├── mapstructure/ │ │ │ ├── encoder.go │ │ │ ├── encoder_test.go │ │ │ └── package_test.go │ │ ├── marshaloption.go │ │ ├── merge.go │ │ ├── metadata/ │ │ │ └── generated_feature_gates.go │ │ ├── testdata/ │ │ │ ├── basic_types.yaml │ │ │ ├── config.yaml │ │ │ ├── config2.yaml │ │ │ └── embedded_keys.yaml │ │ ├── third_party/ │ │ │ └── composehook/ │ │ │ └── compose_hook.go │ │ └── unmarshaloption.go │ ├── metadata.yaml │ ├── provider/ │ │ ├── envprovider/ │ │ │ ├── Makefile │ │ │ ├── README.md │ │ │ ├── generated_package_test.go │ │ │ ├── go.mod │ │ │ ├── go.sum │ │ │ ├── metadata.yaml │ │ │ ├── provider.go │ │ │ └── provider_test.go │ │ ├── fileprovider/ │ │ │ ├── Makefile │ │ │ ├── README.md │ │ │ ├── generated_package_test.go │ │ │ ├── go.mod │ │ │ ├── go.sum │ │ │ ├── metadata.yaml │ │ │ ├── provider.go │ │ │ ├── provider_test.go │ │ │ └── testdata/ │ │ │ ├── default-config.yaml │ │ │ └── invalid-yaml.yaml │ │ ├── httpprovider/ │ │ │ ├── Makefile │ │ │ ├── README.md │ │ │ ├── generated_package_test.go │ │ │ ├── go.mod │ │ │ ├── go.sum │ │ │ ├── metadata.yaml │ │ │ ├── provider.go │ │ │ └── provider_test.go │ │ ├── httpsprovider/ │ │ │ ├── Makefile │ │ │ ├── README.md │ │ │ ├── generated_package_test.go │ │ │ ├── go.mod │ │ │ ├── go.sum │ │ │ ├── metadata.yaml │ │ │ ├── provider.go │ │ │ └── provider_test.go │ │ ├── internal/ │ │ │ ├── configurablehttpprovider/ │ │ │ │ ├── package_test.go │ │ │ │ ├── provider.go │ │ │ │ ├── provider_test.go │ │ │ │ └── testdata/ │ │ │ │ └── otel-config.yaml │ │ │ └── package_test.go │ │ └── yamlprovider/ │ │ ├── Makefile │ │ ├── README.md │ │ ├── generated_package_test.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── metadata.yaml │ │ ├── provider.go │ │ └── provider_test.go │ ├── provider.go │ ├── provider_test.go │ ├── resolver.go │ ├── resolver_test.go │ ├── testdata/ │ │ ├── config.yaml │ │ ├── expand-with-all-env.yaml │ │ ├── expand-with-no-env.yaml │ │ ├── expand-with-partial-env.yaml │ │ ├── merge-append-scenarios-featuregate-disabled.yaml │ │ └── merge-append-scenarios.yaml │ └── xconfmap/ │ ├── Makefile │ ├── config.go │ ├── config_test.go │ ├── confmap.go │ ├── example_test.go │ ├── go.mod │ ├── go.sum │ └── metadata.yaml ├── connector/ │ ├── Makefile │ ├── README.md │ ├── connector.go │ ├── connector_test.go │ ├── connectortest/ │ │ ├── Makefile │ │ ├── connector.go │ │ ├── connector_test.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── metadata.yaml │ │ └── package_test.go │ ├── forwardconnector/ │ │ ├── Makefile │ │ ├── README.md │ │ ├── doc.go │ │ ├── forward.go │ │ ├── forward_test.go │ │ ├── generated_component_test.go │ │ ├── generated_package_test.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── internal/ │ │ │ └── metadata/ │ │ │ └── generated_status.go │ │ └── metadata.yaml │ ├── go.mod │ ├── go.sum │ ├── internal/ │ │ ├── factory.go │ │ └── router.go │ ├── logs_router.go │ ├── logs_router_test.go │ ├── metadata.yaml │ ├── metrics_router.go │ ├── metrics_router_test.go │ ├── package_test.go │ ├── traces_router.go │ ├── traces_router_test.go │ └── xconnector/ │ ├── Makefile │ ├── connector.go │ ├── connector_test.go │ ├── go.mod │ ├── go.sum │ ├── metadata.yaml │ ├── profiles_router.go │ └── profiles_router_test.go ├── consumer/ │ ├── Makefile │ ├── consumer.go │ ├── consumererror/ │ │ ├── Makefile │ │ ├── doc.go │ │ ├── downstream.go │ │ ├── downstream_test.go │ │ ├── error.go │ │ ├── error_test.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── internal/ │ │ │ ├── retryable.go │ │ │ └── statusconversion/ │ │ │ ├── conversion.go │ │ │ └── conversion_test.go │ │ ├── metadata.yaml │ │ ├── package_test.go │ │ ├── permanent.go │ │ ├── permanent_test.go │ │ ├── signalerrors.go │ │ ├── signalerrors_test.go │ │ └── xconsumererror/ │ │ ├── Makefile │ │ ├── go.mod │ │ ├── go.sum │ │ ├── metadata.yaml │ │ ├── signalerrors.go │ │ └── signalerrors_test.go │ ├── consumertest/ │ │ ├── Makefile │ │ ├── consumer.go │ │ ├── doc.go │ │ ├── err.go │ │ ├── err_test.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── metadata.yaml │ │ ├── nop.go │ │ ├── nop_test.go │ │ ├── package_test.go │ │ ├── sink.go │ │ └── sink_test.go │ ├── doc.go │ ├── go.mod │ ├── go.sum │ ├── internal/ │ │ └── consumer.go │ ├── logs.go │ ├── logs_test.go │ ├── metadata.yaml │ ├── metrics.go │ ├── metrics_test.go │ ├── package_test.go │ ├── traces.go │ ├── traces_test.go │ └── xconsumer/ │ ├── Makefile │ ├── go.mod │ ├── go.sum │ ├── metadata.yaml │ ├── profiles.go │ └── profiles_test.go ├── distributions.yaml ├── docs/ │ ├── README.md │ ├── coding-guidelines.md │ ├── component-stability.md │ ├── component-status.md │ ├── ga-roadmap.md │ ├── internal-architecture.md │ ├── observability.md │ ├── platform-support.md │ ├── release.md │ ├── rfcs/ │ │ ├── README.md │ │ ├── component-configuration-schema-roadmap.md │ │ ├── component-status-reporting.md │ │ ├── component-universal-telemetry.md │ │ ├── configuration-merging-strategy.md │ │ ├── configuring-confmap-providers.md │ │ ├── env-vars.md │ │ ├── experimental-profiling.md │ │ ├── logging-before-config-resolution.md │ │ ├── metadata.yaml │ │ ├── optional-config-type.md │ │ ├── processing.md │ │ ├── release-approvers.md │ │ └── semconv-feature-gates.md │ ├── scraping-receivers.md │ ├── security-best-practices.md │ ├── standard-warnings.md │ └── vision.md ├── examples/ │ ├── README.md │ ├── k8s/ │ │ └── otel-config.yaml │ └── local/ │ └── otel-config.yaml ├── exporter/ │ ├── Makefile │ ├── README.md │ ├── debugexporter/ │ │ ├── Makefile │ │ ├── README.md │ │ ├── config.go │ │ ├── config_test.go │ │ ├── doc.go │ │ ├── exporter.go │ │ ├── exporter_test.go │ │ ├── factory.go │ │ ├── factory_test.go │ │ ├── generated_component_test.go │ │ ├── generated_package_test.go │ │ ├── generating-example-output.md │ │ ├── go.mod │ │ ├── go.sum │ │ ├── internal/ │ │ │ ├── metadata/ │ │ │ │ └── generated_status.go │ │ │ ├── normal/ │ │ │ │ ├── common.go │ │ │ │ ├── logs.go │ │ │ │ ├── logs_test.go │ │ │ │ ├── metrics.go │ │ │ │ ├── metrics_test.go │ │ │ │ ├── profiles.go │ │ │ │ ├── profiles_test.go │ │ │ │ ├── traces.go │ │ │ │ └── traces_test.go │ │ │ └── otlptext/ │ │ │ ├── databuffer.go │ │ │ ├── databuffer_test.go │ │ │ ├── known_sync_error.go │ │ │ ├── known_sync_error_other.go │ │ │ ├── known_sync_error_windows.go │ │ │ ├── logs.go │ │ │ ├── logs_test.go │ │ │ ├── metrics.go │ │ │ ├── metrics_test.go │ │ │ ├── package_test.go │ │ │ ├── profiles.go │ │ │ ├── profiles_test.go │ │ │ ├── sync.go │ │ │ ├── test_helpers.go │ │ │ ├── testdata/ │ │ │ │ ├── logs/ │ │ │ │ │ ├── embedded_maps.out │ │ │ │ │ ├── empty.out │ │ │ │ │ ├── log_with_event_name.out │ │ │ │ │ ├── logs_with_entity_refs.out │ │ │ │ │ ├── one_record.out │ │ │ │ │ └── two_records.out │ │ │ │ ├── metrics/ │ │ │ │ │ ├── empty.out │ │ │ │ │ ├── invalid_metric_type.out │ │ │ │ │ ├── metrics_with_all_types.out │ │ │ │ │ ├── metrics_with_entity_refs.out │ │ │ │ │ ├── metrics_with_metadata.out │ │ │ │ │ └── two_metrics.out │ │ │ │ ├── profiles/ │ │ │ │ │ ├── empty.out │ │ │ │ │ ├── profiles_with_entity_refs.out │ │ │ │ │ └── two_profiles.out │ │ │ │ └── traces/ │ │ │ │ ├── empty.out │ │ │ │ ├── traces_with_entity_refs.out │ │ │ │ └── two_spans.out │ │ │ ├── traces.go │ │ │ └── traces_test.go │ │ ├── metadata.yaml │ │ └── testdata/ │ │ ├── config_output_paths.yaml │ │ ├── config_output_paths_empty.yaml │ │ ├── config_verbosity.yaml │ │ └── config_verbosity_typo.yaml │ ├── example_test.go │ ├── exporter.go │ ├── exporter_test.go │ ├── exporterhelper/ │ │ ├── Makefile │ │ ├── README.md │ │ ├── common.go │ │ ├── config.schema.yaml │ │ ├── constants.go │ │ ├── doc.go │ │ ├── documentation.md │ │ ├── generated_package_test.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── internal/ │ │ │ ├── base_exporter.go │ │ │ ├── base_exporter_test.go │ │ │ ├── config.schema.yaml │ │ │ ├── constants.go │ │ │ ├── experr/ │ │ │ │ ├── err.go │ │ │ │ └── err_test.go │ │ │ ├── hosttest/ │ │ │ │ ├── hosttest.go │ │ │ │ └── hosttest_test.go │ │ │ ├── metadata/ │ │ │ │ ├── generated_feature_gates.go │ │ │ │ ├── generated_telemetry.go │ │ │ │ └── generated_telemetry_test.go │ │ │ ├── metadatatest/ │ │ │ │ ├── generated_telemetrytest.go │ │ │ │ └── generated_telemetrytest_test.go │ │ │ ├── new_request.go │ │ │ ├── new_request_test.go │ │ │ ├── obs_report_sender.go │ │ │ ├── obs_report_sender_test.go │ │ │ ├── oteltest/ │ │ │ │ └── tracetest.go │ │ │ ├── package_test.go │ │ │ ├── queue/ │ │ │ │ ├── async_queue.go │ │ │ │ ├── async_queue_test.go │ │ │ │ ├── cond.go │ │ │ │ ├── fg.go │ │ │ │ ├── memory_queue.go │ │ │ │ ├── memory_queue_test.go │ │ │ │ ├── meta.pb.go │ │ │ │ ├── meta.proto │ │ │ │ ├── obs_queue.go │ │ │ │ ├── obs_queue_test.go │ │ │ │ ├── persistent_queue.go │ │ │ │ ├── persistent_queue_test.go │ │ │ │ └── queue.go │ │ │ ├── queue_sender.go │ │ │ ├── queue_sender_test.go │ │ │ ├── queuebatch/ │ │ │ │ ├── batch_context.go │ │ │ │ ├── batch_context_test.go │ │ │ │ ├── batcher.go │ │ │ │ ├── config.go │ │ │ │ ├── config.schema.yaml │ │ │ │ ├── config_test.go │ │ │ │ ├── disabled_batcher.go │ │ │ │ ├── disabled_batcher_test.go │ │ │ │ ├── doc.go │ │ │ │ ├── encoding.go │ │ │ │ ├── generated_package_test.go │ │ │ │ ├── logs.go │ │ │ │ ├── logs_batch.go │ │ │ │ ├── logs_batch_test.go │ │ │ │ ├── logs_test.go │ │ │ │ ├── metadata.yaml │ │ │ │ ├── metadata_partitioner.go │ │ │ │ ├── metadata_partitioner_test.go │ │ │ │ ├── metrics.go │ │ │ │ ├── metrics_batch.go │ │ │ │ ├── metrics_batch_test.go │ │ │ │ ├── metrics_test.go │ │ │ │ ├── multi_batcher.go │ │ │ │ ├── multi_batcher_test.go │ │ │ │ ├── partition_batcher.go │ │ │ │ ├── partition_batcher_test.go │ │ │ │ ├── partitioner.go │ │ │ │ ├── partitioner_test.go │ │ │ │ ├── queue_batch.go │ │ │ │ ├── queue_batch_test.go │ │ │ │ ├── testdata/ │ │ │ │ │ ├── batch_set_empty_explicit_sizer.yaml │ │ │ │ │ ├── batch_set_empty_no_explicit_sizer.yaml │ │ │ │ │ ├── batch_set_nonempty_explicit_sizer.yaml │ │ │ │ │ ├── batch_set_nonempty_no_explicit_sizer.yaml │ │ │ │ │ └── batch_unset.yaml │ │ │ │ ├── traces.go │ │ │ │ ├── traces_batch.go │ │ │ │ ├── traces_batch_test.go │ │ │ │ └── traces_test.go │ │ │ ├── request/ │ │ │ │ ├── request.go │ │ │ │ ├── sizer.go │ │ │ │ └── sizer_test.go │ │ │ ├── requesttest/ │ │ │ │ ├── request.go │ │ │ │ └── sink.go │ │ │ ├── retry_sender.go │ │ │ ├── retry_sender_test.go │ │ │ ├── sender/ │ │ │ │ ├── sender.go │ │ │ │ └── sender_test.go │ │ │ ├── sendertest/ │ │ │ │ ├── sendertest.go │ │ │ │ └── sendertest_test.go │ │ │ ├── sizer/ │ │ │ │ ├── logs_sizer.go │ │ │ │ ├── logs_sizer_test.go │ │ │ │ ├── metrics_sizer.go │ │ │ │ ├── metrics_sizer_test.go │ │ │ │ ├── profiles_sizer.go │ │ │ │ ├── proto_delta_sizer.go │ │ │ │ ├── proto_delta_sizer_test.go │ │ │ │ ├── traces_sizer.go │ │ │ │ └── traces_sizer_test.go │ │ │ ├── storagetest/ │ │ │ │ └── mock_storage.go │ │ │ ├── timeout_sender.go │ │ │ └── timeout_sender_test.go │ │ ├── logs.go │ │ ├── logs_test.go │ │ ├── metadata.yaml │ │ ├── metrics.go │ │ ├── metrics_test.go │ │ ├── queue_batch.go │ │ ├── request.go │ │ ├── retry_sender.go │ │ ├── timeout_sender.go │ │ ├── traces.go │ │ ├── traces_test.go │ │ └── xexporterhelper/ │ │ ├── Makefile │ │ ├── constants.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── metadata.yaml │ │ ├── new_request.go │ │ ├── profiles.go │ │ ├── profiles_batch.go │ │ ├── profiles_batch_test.go │ │ ├── profiles_test.go │ │ └── request.go │ ├── exportertest/ │ │ ├── Makefile │ │ ├── contract_checker.go │ │ ├── contract_checker_test.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── metadata.yaml │ │ ├── mock_consumer.go │ │ ├── mock_consumer_test.go │ │ ├── nop_exporter.go │ │ └── nop_exporter_test.go │ ├── go.mod │ ├── go.sum │ ├── internal/ │ │ └── experr/ │ │ └── err.go │ ├── metadata.yaml │ ├── nopexporter/ │ │ ├── Makefile │ │ ├── README.md │ │ ├── doc.go │ │ ├── generated_component_test.go │ │ ├── generated_package_test.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── internal/ │ │ │ └── metadata/ │ │ │ └── generated_status.go │ │ ├── metadata.yaml │ │ ├── nop_exporter.go │ │ └── nop_exporter_test.go │ ├── otlpexporter/ │ │ ├── Makefile │ │ ├── README.md │ │ ├── cfg-schema.yaml │ │ ├── config.go │ │ ├── config.yaml │ │ ├── config_test.go │ │ ├── doc.go │ │ ├── factory.go │ │ ├── factory_test.go │ │ ├── generated_component_test.go │ │ ├── generated_package_test.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── internal/ │ │ │ └── metadata/ │ │ │ └── generated_status.go │ │ ├── metadata.yaml │ │ ├── otlp.go │ │ ├── otlp_test.go │ │ └── testdata/ │ │ ├── config.yaml │ │ ├── default-batch.yaml │ │ ├── invalid_configs.yaml │ │ ├── test_cert.pem │ │ └── test_key.pem │ ├── otlphttpexporter/ │ │ ├── Makefile │ │ ├── README.md │ │ ├── config.go │ │ ├── config_test.go │ │ ├── doc.go │ │ ├── factory.go │ │ ├── factory_test.go │ │ ├── generated_component_test.go │ │ ├── generated_package_test.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── internal/ │ │ │ └── metadata/ │ │ │ └── generated_status.go │ │ ├── metadata.yaml │ │ ├── otlp.go │ │ ├── otlp_test.go │ │ └── testdata/ │ │ ├── bad_empty_config.yaml │ │ ├── bad_invalid_encoding.yaml │ │ ├── config.yaml │ │ └── test_cert.pem │ ├── package_test.go │ └── xexporter/ │ ├── Makefile │ ├── exporter.go │ ├── exporter_test.go │ ├── go.mod │ ├── go.sum │ └── metadata.yaml ├── extension/ │ ├── Makefile │ ├── README.md │ ├── extension.go │ ├── extension_test.go │ ├── extensionauth/ │ │ ├── Makefile │ │ ├── client.go │ │ ├── client_test.go │ │ ├── doc.go │ │ ├── extensionauthtest/ │ │ │ ├── Makefile │ │ │ ├── err.go │ │ │ ├── err_test.go │ │ │ ├── go.mod │ │ │ ├── go.sum │ │ │ ├── metadata.yaml │ │ │ ├── nop_client.go │ │ │ ├── nop_client_test.go │ │ │ ├── nop_server.go │ │ │ └── package_test.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── metadata.yaml │ │ ├── package_test.go │ │ ├── server.go │ │ └── server_test.go │ ├── extensioncapabilities/ │ │ ├── Makefile │ │ ├── go.mod │ │ ├── go.sum │ │ ├── interfaces.go │ │ └── metadata.yaml │ ├── extensionmiddleware/ │ │ ├── Makefile │ │ ├── README.md │ │ ├── client.go │ │ ├── client_test.go │ │ ├── extensionmiddlewaretest/ │ │ │ ├── Makefile │ │ │ ├── err.go │ │ │ ├── err_test.go │ │ │ ├── go.mod │ │ │ ├── go.sum │ │ │ ├── metadata.yaml │ │ │ ├── nop.go │ │ │ └── nop_test.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── metadata.yaml │ │ ├── server.go │ │ └── server_test.go │ ├── extensiontest/ │ │ ├── Makefile │ │ ├── go.mod │ │ ├── go.sum │ │ ├── metadata.yaml │ │ ├── nop_extension.go │ │ └── nop_extension_test.go │ ├── go.mod │ ├── go.sum │ ├── memorylimiterextension/ │ │ ├── Makefile │ │ ├── README.md │ │ ├── config.go │ │ ├── factory.go │ │ ├── factory_test.go │ │ ├── generated_component_test.go │ │ ├── generated_package_test.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── internal/ │ │ │ └── metadata/ │ │ │ └── generated_status.go │ │ ├── memorylimiter.go │ │ ├── memorylimiter_test.go │ │ ├── metadata.yaml │ │ └── testdata/ │ │ └── config.yaml │ ├── metadata.yaml │ ├── package_test.go │ ├── xextension/ │ │ ├── Makefile │ │ ├── extension.go │ │ ├── extension_test.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── metadata.yaml │ │ └── storage/ │ │ ├── README.md │ │ ├── doc.go │ │ ├── metadata.yaml │ │ ├── nop_client.go │ │ └── storage.go │ └── zpagesextension/ │ ├── Makefile │ ├── README.md │ ├── config.go │ ├── config_test.go │ ├── doc.go │ ├── factory.go │ ├── factory_test.go │ ├── generated_component_test.go │ ├── generated_package_test.go │ ├── go.mod │ ├── go.sum │ ├── internal/ │ │ └── metadata/ │ │ └── generated_status.go │ ├── metadata.yaml │ ├── testdata/ │ │ └── config.yaml │ ├── zpagesextension.go │ └── zpagesextension_test.go ├── featuregate/ │ ├── Makefile │ ├── README.md │ ├── examples_test.go │ ├── flag.go │ ├── flag_test.go │ ├── gate.go │ ├── gate_test.go │ ├── go.mod │ ├── go.sum │ ├── metadata.yaml │ ├── package_test.go │ ├── registry.go │ ├── registry_test.go │ ├── stage.go │ └── stage_test.go ├── filter/ │ ├── Makefile │ ├── config.go │ ├── config.schema.yaml │ ├── config_test.go │ ├── doc.go │ ├── filter.go │ ├── go.mod │ ├── go.sum │ ├── metadata.yaml │ └── testdata/ │ ├── config.yaml │ └── config_invalid.yaml ├── go.mod ├── go.sum ├── internal/ │ ├── buildscripts/ │ │ ├── compare-apidiff.sh │ │ ├── gen-apidiff.sh │ │ └── gen-certs.sh │ ├── cmd/ │ │ └── pdatagen/ │ │ ├── Makefile │ │ ├── go.mod │ │ ├── go.sum │ │ ├── internal/ │ │ │ ├── pdata/ │ │ │ │ ├── base_slices.go │ │ │ │ ├── base_struct.go │ │ │ │ ├── field.go │ │ │ │ ├── message_field.go │ │ │ │ ├── one_of_field.go │ │ │ │ ├── one_of_message_value.go │ │ │ │ ├── one_of_primitive_value.go │ │ │ │ ├── optional_primitive_field.go │ │ │ │ ├── packages.go │ │ │ │ ├── pcommon_package.go │ │ │ │ ├── plog_package.go │ │ │ │ ├── plogotlp_package.go │ │ │ │ ├── pmetric_package.go │ │ │ │ ├── pmetricotlp_package.go │ │ │ │ ├── pprofile_package.go │ │ │ │ ├── pprofileotlp_package.go │ │ │ │ ├── primitive_field.go │ │ │ │ ├── primitive_slice_structs.go │ │ │ │ ├── ptrace_package.go │ │ │ │ ├── ptraceotlp_package.go │ │ │ │ ├── request_package.go │ │ │ │ ├── slice_field.go │ │ │ │ ├── templates/ │ │ │ │ │ ├── message.go.tmpl │ │ │ │ │ ├── message_internal.go.tmpl │ │ │ │ │ ├── message_test.go.tmpl │ │ │ │ │ ├── primitive_slice.go.tmpl │ │ │ │ │ ├── primitive_slice_internal.go.tmpl │ │ │ │ │ ├── primitive_slice_test.go.tmpl │ │ │ │ │ ├── slice.go.tmpl │ │ │ │ │ ├── slice_internal.go.tmpl │ │ │ │ │ └── slice_test.go.tmpl │ │ │ │ ├── templates.go │ │ │ │ ├── typed_field.go │ │ │ │ └── xpdata_entity_package.go │ │ │ ├── proto/ │ │ │ │ ├── copy.go │ │ │ │ ├── delete.go │ │ │ │ ├── enum.go │ │ │ │ ├── field.go │ │ │ │ ├── json_marshal.go │ │ │ │ ├── json_unmarshal.go │ │ │ │ ├── message.go │ │ │ │ ├── metadata.go │ │ │ │ ├── oneof_message.go │ │ │ │ ├── pools.go │ │ │ │ ├── proto.go │ │ │ │ ├── proto_marshal.go │ │ │ │ ├── proto_size.go │ │ │ │ ├── proto_unmarshal.go │ │ │ │ ├── templates/ │ │ │ │ │ ├── message.go.tmpl │ │ │ │ │ └── message_test.go.tmpl │ │ │ │ ├── test_encoding_values.go │ │ │ │ └── wire_type.go │ │ │ └── tmplutil/ │ │ │ └── template.go │ │ └── main.go │ ├── componentalias/ │ │ ├── Makefile │ │ ├── alias.go │ │ ├── alias_test.go │ │ ├── go.mod │ │ └── go.sum │ ├── e2e/ │ │ ├── Makefile │ │ ├── configauth_test.go │ │ ├── configgrpc_test.go │ │ ├── confighttp_test.go │ │ ├── confignet_test.go │ │ ├── configtls_test.go │ │ ├── consume_contract_test.go │ │ ├── error_propagation_test.go │ │ ├── exporter_failure_attributes_test.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── internal_telemetry_test.go │ │ ├── metric_stability_test.go │ │ ├── opaque_test.go │ │ ├── otlphttp_test.go │ │ ├── package_test.go │ │ ├── status_test.go │ │ └── testdata/ │ │ ├── exporter_failure_attributes_test.yaml │ │ ├── metric_stability_test_no_readers.yaml │ │ └── metric_stability_test_readers.yaml │ ├── fanoutconsumer/ │ │ ├── Makefile │ │ ├── go.mod │ │ ├── go.sum │ │ ├── logs.go │ │ ├── logs_test.go │ │ ├── metrics.go │ │ ├── metrics_test.go │ │ ├── package_test.go │ │ ├── profiles.go │ │ ├── profiles_test.go │ │ ├── traces.go │ │ └── traces_test.go │ ├── memorylimiter/ │ │ ├── Makefile │ │ ├── cgroups/ │ │ │ ├── cgroup.go │ │ │ ├── cgroup_test.go │ │ │ ├── cgroups.go │ │ │ ├── cgroups_test.go │ │ │ ├── doc.go │ │ │ ├── errors.go │ │ │ ├── mountpoint.go │ │ │ ├── mountpoint_test.go │ │ │ ├── package_test.go │ │ │ ├── subsys.go │ │ │ ├── subsys_test.go │ │ │ ├── testdata/ │ │ │ │ ├── cgroups/ │ │ │ │ │ ├── cpu/ │ │ │ │ │ │ ├── cpu.cfs_period_us │ │ │ │ │ │ └── cpu.cfs_quota_us │ │ │ │ │ ├── empty/ │ │ │ │ │ │ └── cpu.cfs_quota_us │ │ │ │ │ ├── invalid/ │ │ │ │ │ │ └── cpu.cfs_quota_us │ │ │ │ │ ├── memory/ │ │ │ │ │ │ └── memory.limit_in_bytes │ │ │ │ │ ├── undefined/ │ │ │ │ │ │ ├── cpu.cfs_period_us │ │ │ │ │ │ └── cpu.cfs_quota_us │ │ │ │ │ ├── undefined-period/ │ │ │ │ │ │ └── cpu.cfs_quota_us │ │ │ │ │ └── v2/ │ │ │ │ │ ├── empty/ │ │ │ │ │ │ └── memory.max │ │ │ │ │ ├── invalid/ │ │ │ │ │ │ └── memory.max │ │ │ │ │ ├── memory/ │ │ │ │ │ │ └── memory.max │ │ │ │ │ └── undefined/ │ │ │ │ │ └── memory.max │ │ │ │ └── proc/ │ │ │ │ ├── cgroups/ │ │ │ │ │ ├── cgroup │ │ │ │ │ └── mountinfo │ │ │ │ ├── invalid-cgroup/ │ │ │ │ │ └── cgroup │ │ │ │ ├── invalid-mountinfo/ │ │ │ │ │ └── mountinfo │ │ │ │ ├── untranslatable/ │ │ │ │ │ ├── cgroup │ │ │ │ │ └── mountinfo │ │ │ │ └── v2/ │ │ │ │ ├── cgroupv1/ │ │ │ │ │ └── mountinfo │ │ │ │ ├── cgroupv1v2/ │ │ │ │ │ └── mountinfo │ │ │ │ └── cgroupv2/ │ │ │ │ └── mountinfo │ │ │ └── util_test.go │ │ ├── config.go │ │ ├── config_test.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── iruntime/ │ │ │ ├── mem_info.go │ │ │ ├── mem_info_test.go │ │ │ ├── package_test.go │ │ │ ├── total_memory_linux.go │ │ │ ├── total_memory_linux_test.go │ │ │ ├── total_memory_other.go │ │ │ └── total_memory_other_test.go │ │ ├── memorylimiter.go │ │ ├── memorylimiter_test.go │ │ └── testdata/ │ │ ├── config.yaml │ │ └── negative_unsigned_limits_config.yaml │ ├── sharedcomponent/ │ │ ├── Makefile │ │ ├── go.mod │ │ ├── go.sum │ │ ├── package_test.go │ │ ├── sharedcomponent.go │ │ └── sharedcomponent_test.go │ ├── statusutil/ │ │ ├── helper.go │ │ └── helper_test.go │ ├── telemetry/ │ │ ├── Makefile │ │ ├── attribute.go │ │ ├── attribute_test.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── telemetry.go │ │ └── telemetrytest/ │ │ └── mock.go │ ├── testutil/ │ │ ├── Makefile │ │ ├── README.md │ │ ├── benchmarks.go │ │ ├── fips.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── package_test.go │ │ ├── testutil.go │ │ └── testutil_test.go │ └── tools/ │ ├── Makefile │ ├── go.mod │ └── go.sum ├── otelcol/ │ ├── Makefile │ ├── buffered_core.go │ ├── buffered_core_test.go │ ├── collector.go │ ├── collector_core.go │ ├── collector_core_test.go │ ├── collector_test.go │ ├── collector_windows.go │ ├── collector_windows_service_test.go │ ├── collector_windows_test.go │ ├── command.go │ ├── command_components.go │ ├── command_components_test.go │ ├── command_print.go │ ├── command_print_test.go │ ├── command_test.go │ ├── command_validate.go │ ├── command_validate_test.go │ ├── config.go │ ├── config_test.go │ ├── configprovider.go │ ├── configprovider_test.go │ ├── documentation.md │ ├── factories.go │ ├── factories_test.go │ ├── flags.go │ ├── flags_test.go │ ├── generated_package_test.go │ ├── go.mod │ ├── go.sum │ ├── internal/ │ │ ├── configunmarshaler/ │ │ │ ├── configs.go │ │ │ ├── configs_test.go │ │ │ └── package_test.go │ │ ├── grpclog/ │ │ │ ├── logger.go │ │ │ ├── logger_test.go │ │ │ └── package_test.go │ │ └── metadata/ │ │ └── generated_feature_gates.go │ ├── metadata.yaml │ ├── otelcoltest/ │ │ ├── Makefile │ │ ├── config.go │ │ ├── config_test.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── metadata.yaml │ │ ├── nop_factories.go │ │ ├── nop_factories_test.go │ │ ├── package_test.go │ │ └── testdata/ │ │ ├── config.yaml │ │ ├── config_default_scheme.yaml │ │ └── config_env.yaml │ ├── signals_others.go │ ├── signals_wasm.go │ ├── testdata/ │ │ ├── components-output-sorted.yaml │ │ ├── components-output.yaml │ │ ├── configs/ │ │ │ ├── 1-config-first.yaml │ │ │ ├── 1-config-output.yaml │ │ │ ├── 1-config-second.yaml │ │ │ └── 2-config-output.yaml │ │ ├── otelcol-cyclic-connector.yaml │ │ ├── otelcol-invalid-components.yaml │ │ ├── otelcol-invalid-connector-unused-exp.yaml │ │ ├── otelcol-invalid-connector-unused-rec.yaml │ │ ├── otelcol-invalid-receiver-type.yaml │ │ ├── otelcol-invalid-telemetry.yaml │ │ ├── otelcol-invalid.yaml │ │ ├── otelcol-log-to-file.yaml │ │ ├── otelcol-nop.yaml │ │ ├── otelcol-otelconftelemetry.yaml │ │ ├── otelcol-statuswatcher.yaml │ │ ├── otelcol-valid-connector-use.yaml │ │ ├── print.yaml │ │ ├── print_default.yaml │ │ ├── print_invalid.yaml │ │ └── print_negative.yaml │ ├── unmarshal_dry_run_test.go │ ├── unmarshaler.go │ └── unmarshaler_test.go ├── pdata/ │ ├── Makefile │ ├── README.md │ ├── doc.go │ ├── documentation.md │ ├── generated_package_test.go │ ├── go.mod │ ├── go.sum │ ├── internal/ │ │ ├── .gitignore │ │ ├── bytesid.go │ │ ├── bytesid_test.go │ │ ├── config.schema.yaml │ │ ├── generated_enum_aggregationtemporality.go │ │ ├── generated_enum_severitynumber.go │ │ ├── generated_enum_spankind.go │ │ ├── generated_enum_statuscode.go │ │ ├── generated_proto_anyvalue.go │ │ ├── generated_proto_anyvalue_test.go │ │ ├── generated_proto_arrayvalue.go │ │ ├── generated_proto_arrayvalue_test.go │ │ ├── generated_proto_entityref.go │ │ ├── generated_proto_entityref_test.go │ │ ├── generated_proto_exemplar.go │ │ ├── generated_proto_exemplar_test.go │ │ ├── generated_proto_exponentialhistogram.go │ │ ├── generated_proto_exponentialhistogram_test.go │ │ ├── generated_proto_exponentialhistogramdatapoint.go │ │ ├── generated_proto_exponentialhistogramdatapoint_test.go │ │ ├── generated_proto_exponentialhistogramdatapointbuckets.go │ │ ├── generated_proto_exponentialhistogramdatapointbuckets_test.go │ │ ├── generated_proto_exportlogspartialsuccess.go │ │ ├── generated_proto_exportlogspartialsuccess_test.go │ │ ├── generated_proto_exportlogsservicerequest.go │ │ ├── generated_proto_exportlogsservicerequest_test.go │ │ ├── generated_proto_exportlogsserviceresponse.go │ │ ├── generated_proto_exportlogsserviceresponse_test.go │ │ ├── generated_proto_exportmetricspartialsuccess.go │ │ ├── generated_proto_exportmetricspartialsuccess_test.go │ │ ├── generated_proto_exportmetricsservicerequest.go │ │ ├── generated_proto_exportmetricsservicerequest_test.go │ │ ├── generated_proto_exportmetricsserviceresponse.go │ │ ├── generated_proto_exportmetricsserviceresponse_test.go │ │ ├── generated_proto_exportprofilespartialsuccess.go │ │ ├── generated_proto_exportprofilespartialsuccess_test.go │ │ ├── generated_proto_exportprofilesservicerequest.go │ │ ├── generated_proto_exportprofilesservicerequest_test.go │ │ ├── generated_proto_exportprofilesserviceresponse.go │ │ ├── generated_proto_exportprofilesserviceresponse_test.go │ │ ├── generated_proto_exporttracepartialsuccess.go │ │ ├── generated_proto_exporttracepartialsuccess_test.go │ │ ├── generated_proto_exporttraceservicerequest.go │ │ ├── generated_proto_exporttraceservicerequest_test.go │ │ ├── generated_proto_exporttraceserviceresponse.go │ │ ├── generated_proto_exporttraceserviceresponse_test.go │ │ ├── generated_proto_function.go │ │ ├── generated_proto_function_test.go │ │ ├── generated_proto_gauge.go │ │ ├── generated_proto_gauge_test.go │ │ ├── generated_proto_histogram.go │ │ ├── generated_proto_histogram_test.go │ │ ├── generated_proto_histogramdatapoint.go │ │ ├── generated_proto_histogramdatapoint_test.go │ │ ├── generated_proto_instrumentationscope.go │ │ ├── generated_proto_instrumentationscope_test.go │ │ ├── generated_proto_ipaddr.go │ │ ├── generated_proto_ipaddr_test.go │ │ ├── generated_proto_keyvalue.go │ │ ├── generated_proto_keyvalue_test.go │ │ ├── generated_proto_keyvalueandunit.go │ │ ├── generated_proto_keyvalueandunit_test.go │ │ ├── generated_proto_keyvaluelist.go │ │ ├── generated_proto_keyvaluelist_test.go │ │ ├── generated_proto_line.go │ │ ├── generated_proto_line_test.go │ │ ├── generated_proto_link.go │ │ ├── generated_proto_link_test.go │ │ ├── generated_proto_location.go │ │ ├── generated_proto_location_test.go │ │ ├── generated_proto_logrecord.go │ │ ├── generated_proto_logrecord_test.go │ │ ├── generated_proto_logsdata.go │ │ ├── generated_proto_logsdata_test.go │ │ ├── generated_proto_logsrequest.go │ │ ├── generated_proto_logsrequest_test.go │ │ ├── generated_proto_mapping.go │ │ ├── generated_proto_mapping_test.go │ │ ├── generated_proto_metric.go │ │ ├── generated_proto_metric_test.go │ │ ├── generated_proto_metricsdata.go │ │ ├── generated_proto_metricsdata_test.go │ │ ├── generated_proto_metricsrequest.go │ │ ├── generated_proto_metricsrequest_test.go │ │ ├── generated_proto_numberdatapoint.go │ │ ├── generated_proto_numberdatapoint_test.go │ │ ├── generated_proto_profile.go │ │ ├── generated_proto_profile_test.go │ │ ├── generated_proto_profilesdata.go │ │ ├── generated_proto_profilesdata_test.go │ │ ├── generated_proto_profilesdictionary.go │ │ ├── generated_proto_profilesdictionary_test.go │ │ ├── generated_proto_profilesrequest.go │ │ ├── generated_proto_profilesrequest_test.go │ │ ├── generated_proto_requestcontext.go │ │ ├── generated_proto_requestcontext_test.go │ │ ├── generated_proto_resource.go │ │ ├── generated_proto_resource_test.go │ │ ├── generated_proto_resourcelogs.go │ │ ├── generated_proto_resourcelogs_test.go │ │ ├── generated_proto_resourcemetrics.go │ │ ├── generated_proto_resourcemetrics_test.go │ │ ├── generated_proto_resourceprofiles.go │ │ ├── generated_proto_resourceprofiles_test.go │ │ ├── generated_proto_resourcespans.go │ │ ├── generated_proto_resourcespans_test.go │ │ ├── generated_proto_sample.go │ │ ├── generated_proto_sample_test.go │ │ ├── generated_proto_scopelogs.go │ │ ├── generated_proto_scopelogs_test.go │ │ ├── generated_proto_scopemetrics.go │ │ ├── generated_proto_scopemetrics_test.go │ │ ├── generated_proto_scopeprofiles.go │ │ ├── generated_proto_scopeprofiles_test.go │ │ ├── generated_proto_scopespans.go │ │ ├── generated_proto_scopespans_test.go │ │ ├── generated_proto_span.go │ │ ├── generated_proto_span_test.go │ │ ├── generated_proto_spancontext.go │ │ ├── generated_proto_spancontext_test.go │ │ ├── generated_proto_spanevent.go │ │ ├── generated_proto_spanevent_test.go │ │ ├── generated_proto_spanlink.go │ │ ├── generated_proto_spanlink_test.go │ │ ├── generated_proto_stack.go │ │ ├── generated_proto_stack_test.go │ │ ├── generated_proto_status.go │ │ ├── generated_proto_status_test.go │ │ ├── generated_proto_sum.go │ │ ├── generated_proto_sum_test.go │ │ ├── generated_proto_summary.go │ │ ├── generated_proto_summary_test.go │ │ ├── generated_proto_summarydatapoint.go │ │ ├── generated_proto_summarydatapoint_test.go │ │ ├── generated_proto_summarydatapointvalueatquantile.go │ │ ├── generated_proto_summarydatapointvalueatquantile_test.go │ │ ├── generated_proto_tcpaddr.go │ │ ├── generated_proto_tcpaddr_test.go │ │ ├── generated_proto_tracesdata.go │ │ ├── generated_proto_tracesdata_test.go │ │ ├── generated_proto_tracesrequest.go │ │ ├── generated_proto_tracesrequest_test.go │ │ ├── generated_proto_udpaddr.go │ │ ├── generated_proto_udpaddr_test.go │ │ ├── generated_proto_unixaddr.go │ │ ├── generated_proto_unixaddr_test.go │ │ ├── generated_proto_valuetype.go │ │ ├── generated_proto_valuetype_test.go │ │ ├── generated_wrapper_anyvalueslice.go │ │ ├── generated_wrapper_byteslice.go │ │ ├── generated_wrapper_entityref.go │ │ ├── generated_wrapper_entityrefslice.go │ │ ├── generated_wrapper_exportlogsservicerequest.go │ │ ├── generated_wrapper_exportmetricsservicerequest.go │ │ ├── generated_wrapper_exportprofilesservicerequest.go │ │ ├── generated_wrapper_exporttraceservicerequest.go │ │ ├── generated_wrapper_float64slice.go │ │ ├── generated_wrapper_instrumentationscope.go │ │ ├── generated_wrapper_int32slice.go │ │ ├── generated_wrapper_int64slice.go │ │ ├── generated_wrapper_profilesdata.go │ │ ├── generated_wrapper_resource.go │ │ ├── generated_wrapper_stringslice.go │ │ ├── generated_wrapper_uint64slice.go │ │ ├── json/ │ │ │ ├── iterator.go │ │ │ ├── iterator_test.go │ │ │ ├── package_test.go │ │ │ ├── stream.go │ │ │ └── stream_test.go │ │ ├── metadata/ │ │ │ └── generated_feature_gates.go │ │ ├── otelgrpc/ │ │ │ ├── encoding.go │ │ │ ├── logs_service.go │ │ │ ├── metrics_service.go │ │ │ ├── profiles_service.go │ │ │ └── trace_service.go │ │ ├── otlp/ │ │ │ ├── logs.go │ │ │ ├── logs_test.go │ │ │ ├── metrics.go │ │ │ ├── metrics_test.go │ │ │ ├── package_test.go │ │ │ ├── profiles.go │ │ │ ├── profiles_test.go │ │ │ ├── traces.go │ │ │ └── traces_test.go │ │ ├── profileid.go │ │ ├── profileid_test.go │ │ ├── proto/ │ │ │ ├── marshal.go │ │ │ ├── size.go │ │ │ └── unmarshal.go │ │ ├── spanid.go │ │ ├── spanid_test.go │ │ ├── state.go │ │ ├── state_test.go │ │ ├── traceid.go │ │ ├── traceid_test.go │ │ ├── unpacked_unmarshal_test.go │ │ ├── wrapper_logs.go │ │ ├── wrapper_map.go │ │ ├── wrapper_metrics.go │ │ ├── wrapper_profiles.go │ │ ├── wrapper_traces.go │ │ ├── wrapper_tracestate.go │ │ ├── wrapper_value.go │ │ └── wrapper_value_test.go │ ├── metadata.yaml │ ├── pcommon/ │ │ ├── generated_byteslice.go │ │ ├── generated_byteslice_test.go │ │ ├── generated_float64slice.go │ │ ├── generated_float64slice_test.go │ │ ├── generated_instrumentationscope.go │ │ ├── generated_instrumentationscope_test.go │ │ ├── generated_int32slice.go │ │ ├── generated_int32slice_test.go │ │ ├── generated_int64slice.go │ │ ├── generated_int64slice_test.go │ │ ├── generated_resource.go │ │ ├── generated_resource_test.go │ │ ├── generated_slice.go │ │ ├── generated_slice_test.go │ │ ├── generated_stringslice.go │ │ ├── generated_stringslice_test.go │ │ ├── generated_uint64slice.go │ │ ├── generated_uint64slice_test.go │ │ ├── map.go │ │ ├── map_test.go │ │ ├── package_test.go │ │ ├── slice.go │ │ ├── slice_test.go │ │ ├── spanid.go │ │ ├── spanid_test.go │ │ ├── timestamp.go │ │ ├── timestamp_test.go │ │ ├── trace_state.go │ │ ├── trace_state_test.go │ │ ├── traceid.go │ │ ├── traceid_test.go │ │ ├── value.go │ │ └── value_test.go │ ├── plog/ │ │ ├── config.schema.yaml │ │ ├── doc_test.go │ │ ├── encoding.go │ │ ├── fuzz_test.go │ │ ├── generated_logrecord.go │ │ ├── generated_logrecord_test.go │ │ ├── generated_logrecordslice.go │ │ ├── generated_logrecordslice_test.go │ │ ├── generated_logs.go │ │ ├── generated_logs_test.go │ │ ├── generated_resourcelogs.go │ │ ├── generated_resourcelogs_test.go │ │ ├── generated_resourcelogsslice.go │ │ ├── generated_resourcelogsslice_test.go │ │ ├── generated_scopelogs.go │ │ ├── generated_scopelogs_test.go │ │ ├── generated_scopelogsslice.go │ │ ├── generated_scopelogsslice_test.go │ │ ├── json.go │ │ ├── log_record_flags.go │ │ ├── log_record_flags_test.go │ │ ├── logs.go │ │ ├── logs_test.go │ │ ├── package_test.go │ │ ├── pb.go │ │ ├── pb_test.go │ │ ├── plogotlp/ │ │ │ ├── fuzz_test.go │ │ │ ├── generated_exportpartialsuccess.go │ │ │ ├── generated_exportpartialsuccess_test.go │ │ │ ├── generated_exportresponse.go │ │ │ ├── generated_exportresponse_test.go │ │ │ ├── grpc.go │ │ │ ├── grpc_test.go │ │ │ ├── package_test.go │ │ │ ├── request.go │ │ │ ├── request_test.go │ │ │ ├── response.go │ │ │ └── response_test.go │ │ ├── severity_number.go │ │ └── severity_number_test.go │ ├── pmetric/ │ │ ├── aggregation_temporality.go │ │ ├── aggregation_temporality_test.go │ │ ├── doc_test.go │ │ ├── encoding.go │ │ ├── exemplar_value_type.go │ │ ├── exemplar_value_type_test.go │ │ ├── fuzz_test.go │ │ ├── generated_exemplar.go │ │ ├── generated_exemplar_test.go │ │ ├── generated_exemplarslice.go │ │ ├── generated_exemplarslice_test.go │ │ ├── generated_exponentialhistogram.go │ │ ├── generated_exponentialhistogram_test.go │ │ ├── generated_exponentialhistogramdatapoint.go │ │ ├── generated_exponentialhistogramdatapoint_test.go │ │ ├── generated_exponentialhistogramdatapointbuckets.go │ │ ├── generated_exponentialhistogramdatapointbuckets_test.go │ │ ├── generated_exponentialhistogramdatapointslice.go │ │ ├── generated_exponentialhistogramdatapointslice_test.go │ │ ├── generated_gauge.go │ │ ├── generated_gauge_test.go │ │ ├── generated_histogram.go │ │ ├── generated_histogram_test.go │ │ ├── generated_histogramdatapoint.go │ │ ├── generated_histogramdatapoint_test.go │ │ ├── generated_histogramdatapointslice.go │ │ ├── generated_histogramdatapointslice_test.go │ │ ├── generated_metric.go │ │ ├── generated_metric_test.go │ │ ├── generated_metrics.go │ │ ├── generated_metrics_test.go │ │ ├── generated_metricslice.go │ │ ├── generated_metricslice_test.go │ │ ├── generated_numberdatapoint.go │ │ ├── generated_numberdatapoint_test.go │ │ ├── generated_numberdatapointslice.go │ │ ├── generated_numberdatapointslice_test.go │ │ ├── generated_resourcemetrics.go │ │ ├── generated_resourcemetrics_test.go │ │ ├── generated_resourcemetricsslice.go │ │ ├── generated_resourcemetricsslice_test.go │ │ ├── generated_scopemetrics.go │ │ ├── generated_scopemetrics_test.go │ │ ├── generated_scopemetricsslice.go │ │ ├── generated_scopemetricsslice_test.go │ │ ├── generated_sum.go │ │ ├── generated_sum_test.go │ │ ├── generated_summary.go │ │ ├── generated_summary_test.go │ │ ├── generated_summarydatapoint.go │ │ ├── generated_summarydatapoint_test.go │ │ ├── generated_summarydatapointslice.go │ │ ├── generated_summarydatapointslice_test.go │ │ ├── generated_summarydatapointvalueatquantile.go │ │ ├── generated_summarydatapointvalueatquantile_test.go │ │ ├── generated_summarydatapointvalueatquantileslice.go │ │ ├── generated_summarydatapointvalueatquantileslice_test.go │ │ ├── json.go │ │ ├── metric_data_point_flags.go │ │ ├── metric_data_point_flags_test.go │ │ ├── metric_type.go │ │ ├── metric_type_test.go │ │ ├── metrics.go │ │ ├── metrics_test.go │ │ ├── number_data_point_value_type.go │ │ ├── number_data_point_value_type_test.go │ │ ├── package_test.go │ │ ├── pb.go │ │ ├── pb_test.go │ │ └── pmetricotlp/ │ │ ├── fuzz_test.go │ │ ├── generated_exportpartialsuccess.go │ │ ├── generated_exportpartialsuccess_test.go │ │ ├── generated_exportresponse.go │ │ ├── generated_exportresponse_test.go │ │ ├── grpc.go │ │ ├── grpc_test.go │ │ ├── package_test.go │ │ ├── request.go │ │ ├── request_test.go │ │ ├── response.go │ │ └── response_test.go │ ├── pprofile/ │ │ ├── Makefile │ │ ├── aggregation_temporality.go │ │ ├── aggregation_temporality_test.go │ │ ├── attributes.go │ │ ├── attributes_test.go │ │ ├── dictionary_helpers.go │ │ ├── dictionary_helpers_test.go │ │ ├── encoding.go │ │ ├── function.go │ │ ├── function_test.go │ │ ├── functions.go │ │ ├── functions_test.go │ │ ├── fuzz_test.go │ │ ├── generated_function.go │ │ ├── generated_function_test.go │ │ ├── generated_functionslice.go │ │ ├── generated_functionslice_test.go │ │ ├── generated_keyvalueandunit.go │ │ ├── generated_keyvalueandunit_test.go │ │ ├── generated_keyvalueandunitslice.go │ │ ├── generated_keyvalueandunitslice_test.go │ │ ├── generated_line.go │ │ ├── generated_line_test.go │ │ ├── generated_lineslice.go │ │ ├── generated_lineslice_test.go │ │ ├── generated_link.go │ │ ├── generated_link_test.go │ │ ├── generated_linkslice.go │ │ ├── generated_linkslice_test.go │ │ ├── generated_location.go │ │ ├── generated_location_test.go │ │ ├── generated_locationslice.go │ │ ├── generated_locationslice_test.go │ │ ├── generated_mapping.go │ │ ├── generated_mapping_test.go │ │ ├── generated_mappingslice.go │ │ ├── generated_mappingslice_test.go │ │ ├── generated_profile.go │ │ ├── generated_profile_test.go │ │ ├── generated_profiles.go │ │ ├── generated_profiles_test.go │ │ ├── generated_profilesdata.go │ │ ├── generated_profilesdata_test.go │ │ ├── generated_profilesdictionary.go │ │ ├── generated_profilesdictionary_test.go │ │ ├── generated_profilesslice.go │ │ ├── generated_profilesslice_test.go │ │ ├── generated_resourceprofiles.go │ │ ├── generated_resourceprofiles_test.go │ │ ├── generated_resourceprofilesslice.go │ │ ├── generated_resourceprofilesslice_test.go │ │ ├── generated_sample.go │ │ ├── generated_sample_test.go │ │ ├── generated_sampleslice.go │ │ ├── generated_sampleslice_test.go │ │ ├── generated_scopeprofiles.go │ │ ├── generated_scopeprofiles_test.go │ │ ├── generated_scopeprofilesslice.go │ │ ├── generated_scopeprofilesslice_test.go │ │ ├── generated_stack.go │ │ ├── generated_stack_test.go │ │ ├── generated_stackslice.go │ │ ├── generated_stackslice_test.go │ │ ├── generated_valuetype.go │ │ ├── generated_valuetype_test.go │ │ ├── generated_valuetypeslice.go │ │ ├── generated_valuetypeslice_test.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── json.go │ │ ├── json_references_test.go │ │ ├── keyvalueandunit.go │ │ ├── keyvalueandunit_test.go │ │ ├── line.go │ │ ├── line_test.go │ │ ├── link.go │ │ ├── link_test.go │ │ ├── links.go │ │ ├── links_test.go │ │ ├── location.go │ │ ├── location_test.go │ │ ├── locations.go │ │ ├── locations_test.go │ │ ├── mapping.go │ │ ├── mapping_test.go │ │ ├── mappings.go │ │ ├── mappings_test.go │ │ ├── metadata.yaml │ │ ├── pb.go │ │ ├── pb_references_test.go │ │ ├── pb_test.go │ │ ├── pprofileotlp/ │ │ │ ├── fuzz_test.go │ │ │ ├── generated_exportpartialsuccess.go │ │ │ ├── generated_exportpartialsuccess_test.go │ │ │ ├── generated_exportresponse.go │ │ │ ├── generated_exportresponse_test.go │ │ │ ├── grpc.go │ │ │ ├── grpc_test.go │ │ │ ├── package_test.go │ │ │ ├── request.go │ │ │ ├── request_test.go │ │ │ ├── response.go │ │ │ └── response_test.go │ │ ├── profile.go │ │ ├── profile_test.go │ │ ├── profileid.go │ │ ├── profileid_test.go │ │ ├── profiles.go │ │ ├── profiles_merge.go │ │ ├── profiles_merge_test.go │ │ ├── profiles_test.go │ │ ├── resourceprofiles.go │ │ ├── resourceprofiles_test.go │ │ ├── sample.go │ │ ├── sample_test.go │ │ ├── scopeprofiles.go │ │ ├── scopeprofiles_test.go │ │ ├── stack.go │ │ ├── stack_test.go │ │ ├── stacks.go │ │ ├── stacks_test.go │ │ ├── string_table.go │ │ ├── string_table_test.go │ │ ├── valuetype.go │ │ └── valuetype_test.go │ ├── ptrace/ │ │ ├── doc_test.go │ │ ├── encoding.go │ │ ├── fuzz_test.go │ │ ├── generated_resourcespans.go │ │ ├── generated_resourcespans_test.go │ │ ├── generated_resourcespansslice.go │ │ ├── generated_resourcespansslice_test.go │ │ ├── generated_scopespans.go │ │ ├── generated_scopespans_test.go │ │ ├── generated_scopespansslice.go │ │ ├── generated_scopespansslice_test.go │ │ ├── generated_span.go │ │ ├── generated_span_test.go │ │ ├── generated_spanevent.go │ │ ├── generated_spanevent_test.go │ │ ├── generated_spaneventslice.go │ │ ├── generated_spaneventslice_test.go │ │ ├── generated_spanlink.go │ │ ├── generated_spanlink_test.go │ │ ├── generated_spanlinkslice.go │ │ ├── generated_spanlinkslice_test.go │ │ ├── generated_spanslice.go │ │ ├── generated_spanslice_test.go │ │ ├── generated_status.go │ │ ├── generated_status_test.go │ │ ├── generated_traces.go │ │ ├── generated_traces_test.go │ │ ├── json.go │ │ ├── package_test.go │ │ ├── pb.go │ │ ├── pb_test.go │ │ ├── ptraceotlp/ │ │ │ ├── fuzz_test.go │ │ │ ├── generated_exportpartialsuccess.go │ │ │ ├── generated_exportpartialsuccess_test.go │ │ │ ├── generated_exportresponse.go │ │ │ ├── generated_exportresponse_test.go │ │ │ ├── grpc.go │ │ │ ├── grpc_test.go │ │ │ ├── package_test.go │ │ │ ├── request.go │ │ │ ├── request_test.go │ │ │ ├── response.go │ │ │ └── response_test.go │ │ ├── span_kind.go │ │ ├── span_kind_test.go │ │ ├── status_code.go │ │ ├── status_code_test.go │ │ ├── traces.go │ │ └── traces_test.go │ ├── testdata/ │ │ ├── Makefile │ │ ├── common.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── log.go │ │ ├── metric.go │ │ ├── profile.go │ │ ├── resource.go │ │ └── trace.go │ └── xpdata/ │ ├── Makefile │ ├── documentation.md │ ├── entity/ │ │ ├── entity.go │ │ ├── entity_attribute_map.go │ │ ├── entity_attribute_map_test.go │ │ ├── entity_map.go │ │ ├── entity_map_test.go │ │ ├── entity_test.go │ │ ├── generated_entityref.go │ │ ├── generated_entityref_test.go │ │ ├── generated_entityrefslice.go │ │ ├── generated_entityrefslice_test.go │ │ ├── resource_entities.go │ │ └── resource_entities_test.go │ ├── fuzz_test.go │ ├── generated_package_test.go │ ├── go.mod │ ├── go.sum │ ├── internal/ │ │ └── metadata/ │ │ └── generated_feature_gates.go │ ├── json.go │ ├── json_test.go │ ├── map_builder.go │ ├── map_builder_test.go │ ├── metadata.yaml │ ├── pref/ │ │ ├── gate.go │ │ ├── logs.go │ │ ├── metrics.go │ │ ├── profiles.go │ │ └── traces.go │ ├── request/ │ │ ├── context.go │ │ ├── context_test.go │ │ ├── internal/ │ │ │ └── request.proto │ │ ├── logs_request.go │ │ ├── logs_request_test.go │ │ ├── metrics_request.go │ │ ├── metrics_request_test.go │ │ ├── profiles_request.go │ │ ├── profiles_request_test.go │ │ ├── requesttest.go │ │ ├── traces_request.go │ │ ├── traces_request_test.go │ │ ├── version_check.go │ │ └── version_check_test.go │ └── xpdata.go ├── pipeline/ │ ├── Makefile │ ├── go.mod │ ├── go.sum │ ├── internal/ │ │ └── globalsignal/ │ │ ├── signal.go │ │ └── signal_test.go │ ├── metadata.yaml │ ├── pipeline.go │ ├── pipeline_test.go │ ├── signal.go │ └── xpipeline/ │ ├── Makefile │ ├── config.go │ ├── go.mod │ ├── go.sum │ └── metadata.yaml ├── processor/ │ ├── Makefile │ ├── README.md │ ├── batchprocessor/ │ │ ├── Makefile │ │ ├── README.md │ │ ├── batch_processor.go │ │ ├── batch_processor_test.go │ │ ├── config.go │ │ ├── config_test.go │ │ ├── documentation.md │ │ ├── factory.go │ │ ├── factory_test.go │ │ ├── generated_component_test.go │ │ ├── generated_package_test.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── internal/ │ │ │ ├── metadata/ │ │ │ │ ├── generated_status.go │ │ │ │ ├── generated_telemetry.go │ │ │ │ └── generated_telemetry_test.go │ │ │ └── metadatatest/ │ │ │ ├── generated_telemetrytest.go │ │ │ └── generated_telemetrytest_test.go │ │ ├── metadata.yaml │ │ ├── metrics.go │ │ ├── splitlogs.go │ │ ├── splitlogs_test.go │ │ ├── splitmetrics.go │ │ ├── splitmetrics_test.go │ │ ├── splittraces.go │ │ ├── splittraces_test.go │ │ └── testdata/ │ │ └── config.yaml │ ├── go.mod │ ├── go.sum │ ├── internal/ │ │ ├── err.go │ │ └── obsmetrics.go │ ├── memorylimiterprocessor/ │ │ ├── Makefile │ │ ├── README.md │ │ ├── config.go │ │ ├── documentation.md │ │ ├── factory.go │ │ ├── factory_test.go │ │ ├── generated_component_test.go │ │ ├── generated_package_test.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── internal/ │ │ │ ├── metadata/ │ │ │ │ ├── generated_status.go │ │ │ │ ├── generated_telemetry.go │ │ │ │ └── generated_telemetry_test.go │ │ │ ├── metadatatest/ │ │ │ │ ├── generated_telemetrytest.go │ │ │ │ └── generated_telemetrytest_test.go │ │ │ ├── mock_exporter.go │ │ │ └── mock_receiver.go │ │ ├── memorylimiter.go │ │ ├── memorylimiter_test.go │ │ ├── metadata.yaml │ │ └── obsreport.go │ ├── metadata.yaml │ ├── package_test.go │ ├── processor.go │ ├── processor_test.go │ ├── processorhelper/ │ │ ├── Makefile │ │ ├── documentation.md │ │ ├── example_test.go │ │ ├── generated_package_test.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── internal/ │ │ │ ├── metadata/ │ │ │ │ ├── generated_telemetry.go │ │ │ │ └── generated_telemetry_test.go │ │ │ └── metadatatest/ │ │ │ ├── generated_telemetrytest.go │ │ │ └── generated_telemetrytest_test.go │ │ ├── logs.go │ │ ├── logs_test.go │ │ ├── metadata.yaml │ │ ├── metrics.go │ │ ├── metrics_test.go │ │ ├── obsreport.go │ │ ├── processor.go │ │ ├── traces.go │ │ ├── traces_test.go │ │ └── xprocessorhelper/ │ │ ├── Makefile │ │ ├── go.mod │ │ ├── go.sum │ │ ├── metadata.yaml │ │ ├── processor.go │ │ ├── profiles.go │ │ └── profiles_test.go │ ├── processortest/ │ │ ├── Makefile │ │ ├── go.mod │ │ ├── go.sum │ │ ├── metadata.yaml │ │ ├── nop_processor.go │ │ ├── nop_processor_test.go │ │ ├── package_test.go │ │ ├── shutdown_verifier.go │ │ ├── shutdown_verifier_test.go │ │ ├── unhealthy_processor.go │ │ └── unhealthy_processor_test.go │ └── xprocessor/ │ ├── Makefile │ ├── go.mod │ ├── go.sum │ ├── metadata.yaml │ ├── processor.go │ └── processor_test.go ├── receiver/ │ ├── Makefile │ ├── README.md │ ├── doc.go │ ├── example_test.go │ ├── go.mod │ ├── go.sum │ ├── internal/ │ │ └── err.go │ ├── metadata.yaml │ ├── nopreceiver/ │ │ ├── Makefile │ │ ├── README.md │ │ ├── doc.go │ │ ├── generated_component_test.go │ │ ├── generated_package_test.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── internal/ │ │ │ └── metadata/ │ │ │ ├── generated_logs.go │ │ │ ├── generated_logs_test.go │ │ │ └── generated_status.go │ │ ├── metadata.yaml │ │ ├── nop_receiver.go │ │ └── nop_receiver_test.go │ ├── otlpreceiver/ │ │ ├── Makefile │ │ ├── README.md │ │ ├── config.go │ │ ├── config.md │ │ ├── config_test.go │ │ ├── doc.go │ │ ├── encoder.go │ │ ├── factory.go │ │ ├── factory_test.go │ │ ├── fuzz_test.go │ │ ├── generated_component_test.go │ │ ├── generated_package_test.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── internal/ │ │ │ ├── errors/ │ │ │ │ ├── errors.go │ │ │ │ └── errors_test.go │ │ │ ├── logs/ │ │ │ │ ├── otlp.go │ │ │ │ ├── otlp_test.go │ │ │ │ └── package_test.go │ │ │ ├── metadata/ │ │ │ │ ├── generated_logs.go │ │ │ │ ├── generated_logs_test.go │ │ │ │ └── generated_status.go │ │ │ ├── metrics/ │ │ │ │ ├── otlp.go │ │ │ │ ├── otlp_test.go │ │ │ │ └── package_test.go │ │ │ ├── profiles/ │ │ │ │ ├── otlp.go │ │ │ │ ├── otlp_test.go │ │ │ │ └── package_test.go │ │ │ └── trace/ │ │ │ ├── otlp.go │ │ │ ├── otlp_test.go │ │ │ └── package_test.go │ │ ├── metadata.yaml │ │ ├── otlp.go │ │ ├── otlp_benchmark_test.go │ │ ├── otlp_test.go │ │ ├── otlphttp.go │ │ ├── otlphttp_test.go │ │ └── testdata/ │ │ ├── bad_no_proto_config.yaml │ │ ├── bad_proto_config.yaml │ │ ├── config.yaml │ │ ├── default.yaml │ │ ├── invalid_logs_path.yaml │ │ ├── invalid_metrics_path.yaml │ │ ├── invalid_profiles_path.yaml │ │ ├── invalid_traces_path.yaml │ │ ├── only_grpc.yaml │ │ ├── only_http.yaml │ │ ├── only_http_empty_map.yaml │ │ ├── only_http_null.yaml │ │ ├── typo_default_proto_config.yaml │ │ └── uds.yaml │ ├── package_test.go │ ├── receiver.go │ ├── receiver_test.go │ ├── receiverhelper/ │ │ ├── Makefile │ │ ├── documentation.md │ │ ├── featuregates.go │ │ ├── generated_package_test.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── internal/ │ │ │ ├── metadata/ │ │ │ │ ├── generated_feature_gates.go │ │ │ │ ├── generated_telemetry.go │ │ │ │ └── generated_telemetry_test.go │ │ │ ├── metadatatest/ │ │ │ │ ├── generated_telemetrytest.go │ │ │ │ └── generated_telemetrytest_test.go │ │ │ └── obsmetrics.go │ │ ├── metadata.yaml │ │ ├── obsreport.go │ │ └── obsreport_test.go │ ├── receivertest/ │ │ ├── Makefile │ │ ├── contract_checker.go │ │ ├── contract_checker_test.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── metadata.yaml │ │ ├── nop_receiver.go │ │ ├── nop_receiver_test.go │ │ └── package_test.go │ └── xreceiver/ │ ├── Makefile │ ├── go.mod │ ├── go.sum │ ├── metadata.yaml │ ├── receiver.go │ └── receiver_test.go ├── renovate.json ├── reports/ │ └── distributions/ │ ├── contrib.yaml │ ├── core.yaml │ ├── k8s.yaml │ └── otlp.yaml ├── scraper/ │ ├── Makefile │ ├── README.md │ ├── doc.go │ ├── factory.go │ ├── factory_test.go │ ├── generated_package_test.go │ ├── go.mod │ ├── go.sum │ ├── logs.go │ ├── logs_test.go │ ├── metadata.yaml │ ├── metrics.go │ ├── metrics_test.go │ ├── scraper.go │ ├── scrapererror/ │ │ ├── doc.go │ │ ├── package_test.go │ │ ├── partialscrapeerror.go │ │ ├── partialscrapeerror_test.go │ │ ├── scrapeerror.go │ │ └── scrapeerror_test.go │ ├── scraperhelper/ │ │ ├── Makefile │ │ ├── README.md │ │ ├── config.schema.yaml │ │ ├── controller.go │ │ ├── controller_test.go │ │ ├── doc.go │ │ ├── documentation.md │ │ ├── generated_package_test.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── internal/ │ │ │ ├── controller/ │ │ │ │ ├── config.go │ │ │ │ ├── config_test.go │ │ │ │ └── controller.go │ │ │ ├── metadata/ │ │ │ │ ├── generated_telemetry.go │ │ │ │ └── generated_telemetry_test.go │ │ │ ├── metadatatest/ │ │ │ │ ├── generated_telemetrytest.go │ │ │ │ └── generated_telemetrytest_test.go │ │ │ └── testhelper/ │ │ │ └── helper.go │ │ ├── metadata.yaml │ │ ├── obs_logs.go │ │ ├── obs_logs_test.go │ │ ├── obs_metrics.go │ │ ├── obs_metrics_test.go │ │ └── xscraperhelper/ │ │ ├── Makefile │ │ ├── controller.go │ │ ├── controller_test.go │ │ ├── doc.go │ │ ├── documentation.md │ │ ├── generated_package_test.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── internal/ │ │ │ ├── metadata/ │ │ │ │ ├── generated_telemetry.go │ │ │ │ └── generated_telemetry_test.go │ │ │ └── metadatatest/ │ │ │ ├── generated_telemetrytest.go │ │ │ └── generated_telemetrytest_test.go │ │ ├── metadata.yaml │ │ ├── obs_profiles.go │ │ └── obs_profiles_test.go │ ├── scrapertest/ │ │ ├── Makefile │ │ ├── go.mod │ │ ├── go.sum │ │ ├── metadata.yaml │ │ └── settings.go │ └── xscraper/ │ ├── Makefile │ ├── doc.go │ ├── factory.go │ ├── factory_test.go │ ├── generated_package_test.go │ ├── go.mod │ ├── go.sum │ ├── metadata.yaml │ ├── profiles.go │ ├── profiles_test.go │ └── scraper.go ├── service/ │ ├── Makefile │ ├── README.md │ ├── config.go │ ├── config_test.go │ ├── documentation.md │ ├── extensions/ │ │ ├── config.go │ │ ├── extensions.go │ │ ├── extensions_test.go │ │ ├── graph.go │ │ ├── graph_test.go │ │ └── package_test.go │ ├── generated_package_test.go │ ├── go.mod │ ├── go.sum │ ├── hostcapabilities/ │ │ ├── Makefile │ │ ├── go.mod │ │ ├── go.sum │ │ ├── interfaces.go │ │ └── metadata.yaml │ ├── internal/ │ │ ├── attribute/ │ │ │ ├── attribute.go │ │ │ └── attribute_test.go │ │ ├── builders/ │ │ │ ├── builders.go │ │ │ ├── builders_test/ │ │ │ │ ├── connector_test.go │ │ │ │ ├── exporter_test.go │ │ │ │ ├── extension_test.go │ │ │ │ ├── processor_test.go │ │ │ │ └── receiver_test.go │ │ │ ├── builders_test.go │ │ │ ├── connector.go │ │ │ ├── exporter.go │ │ │ ├── extension.go │ │ │ ├── processor.go │ │ │ └── receiver.go │ │ ├── capabilityconsumer/ │ │ │ ├── capabilities.go │ │ │ ├── capabilities_test.go │ │ │ └── package_test.go │ │ ├── componentattribute/ │ │ │ ├── logger_zap.go │ │ │ ├── meter_provider.go │ │ │ ├── telemetry.go │ │ │ ├── telemetry_test.go │ │ │ └── tracer_provider.go │ │ ├── graph/ │ │ │ ├── capabilities.go │ │ │ ├── connector.go │ │ │ ├── consumer.go │ │ │ ├── exporter.go │ │ │ ├── fanout.go │ │ │ ├── graph.go │ │ │ ├── graph_test.go │ │ │ ├── host.go │ │ │ ├── lifecycle_test.go │ │ │ ├── metadata.yaml │ │ │ ├── obs_test.go │ │ │ ├── package_test.go │ │ │ ├── processor.go │ │ │ ├── receiver.go │ │ │ ├── util_test.go │ │ │ └── zpages.go │ │ ├── metadata/ │ │ │ ├── generated_feature_gates.go │ │ │ ├── generated_telemetry.go │ │ │ └── generated_telemetry_test.go │ │ ├── metadatatest/ │ │ │ ├── generated_telemetrytest.go │ │ │ └── generated_telemetrytest_test.go │ │ ├── metricviews/ │ │ │ ├── views.go │ │ │ └── views_test.go │ │ ├── moduleinfo/ │ │ │ └── moduleinfo.go │ │ ├── obsconsumer/ │ │ │ ├── consumer_test.go │ │ │ ├── enabled.go │ │ │ ├── enabled_test.go │ │ │ ├── gate_test.go │ │ │ ├── logs.go │ │ │ ├── logs_test.go │ │ │ ├── metrics.go │ │ │ ├── metrics_test.go │ │ │ ├── option.go │ │ │ ├── package_test.go │ │ │ ├── profiles.go │ │ │ ├── profiles_test.go │ │ │ ├── telemetry.go │ │ │ ├── traces.go │ │ │ └── traces_test.go │ │ ├── proctelemetry/ │ │ │ ├── process_telemetry.go │ │ │ ├── process_telemetry_linux_test.go │ │ │ └── process_telemetry_test.go │ │ ├── promtest/ │ │ │ └── server_util.go │ │ ├── refconsumer/ │ │ │ ├── logs.go │ │ │ ├── logs_test.go │ │ │ ├── metrics.go │ │ │ ├── metrics_test.go │ │ │ ├── profiles.go │ │ │ ├── profiles_test.go │ │ │ ├── traces.go │ │ │ └── traces_test.go │ │ ├── resource/ │ │ │ ├── config.go │ │ │ └── config_test.go │ │ ├── status/ │ │ │ ├── nop.go │ │ │ ├── nop_test.go │ │ │ ├── package_test.go │ │ │ ├── status.go │ │ │ └── status_test.go │ │ ├── testcomponents/ │ │ │ ├── example_connector.go │ │ │ ├── example_connector_test.go │ │ │ ├── example_exporter.go │ │ │ ├── example_exporter_test.go │ │ │ ├── example_processor.go │ │ │ ├── example_processor_test.go │ │ │ ├── example_receiver.go │ │ │ ├── example_receiver_test.go │ │ │ ├── example_router.go │ │ │ ├── example_router_test.go │ │ │ ├── package_test.go │ │ │ └── stateful_component.go │ │ └── zpages/ │ │ ├── package_test.go │ │ ├── templates/ │ │ │ ├── component_header.html │ │ │ ├── extensions_table.html │ │ │ ├── features_table.html │ │ │ ├── page_footer.html │ │ │ ├── page_header.html │ │ │ ├── pipelines_table.html │ │ │ └── properties_table.html │ │ ├── templates.go │ │ └── templates_test.go │ ├── metadata.yaml │ ├── pipelines/ │ │ ├── config.go │ │ ├── config_test.go │ │ └── package_test.go │ ├── service.go │ ├── service_host_test.go │ ├── service_test.go │ └── telemetry/ │ ├── doc.go │ ├── otelconftelemetry/ │ │ ├── config.go │ │ ├── config_test.go │ │ ├── factory.go │ │ ├── factory_test.go │ │ ├── internal/ │ │ │ └── migration/ │ │ │ ├── normalize.go │ │ │ ├── testdata/ │ │ │ │ ├── v0.2.0_logs.yaml │ │ │ │ ├── v0.2.0_metrics.yaml │ │ │ │ ├── v0.2.0_traces.yaml │ │ │ │ ├── v0.3.0_logs.yaml │ │ │ │ ├── v0.3.0_metrics.yaml │ │ │ │ └── v0.3.0_traces.yaml │ │ │ ├── v0.2.0.go │ │ │ ├── v0.2.0_test.go │ │ │ ├── v0.3.0.go │ │ │ └── v0.3.0_test.go │ │ ├── logger.go │ │ ├── logger_tee.go │ │ ├── logger_test.go │ │ ├── metrics.go │ │ ├── metrics_test.go │ │ ├── package_test.go │ │ ├── resource.go │ │ ├── resource_test.go │ │ ├── sdk.go │ │ ├── testdata/ │ │ │ ├── config_deprecated_address.yaml │ │ │ ├── config_deprecated_address_and_readers.yaml │ │ │ ├── config_empty.yaml │ │ │ ├── config_empty_readers.yaml │ │ │ ├── config_invalid_deprecated_address.yaml │ │ │ ├── config_invalid_metrics_empty_readers.yaml │ │ │ ├── config_invalid_metrics_views_feature_gate.yaml │ │ │ ├── config_invalid_metrics_views_level.yaml │ │ │ ├── config_invalid_unknown_field.yaml │ │ │ ├── config_logs.yaml │ │ │ └── config_metrics_empty_readers.yaml │ │ ├── tracer.go │ │ └── tracer_test.go │ ├── telemetry.go │ ├── telemetry_test.go │ └── telemetrytest/ │ ├── Makefile │ ├── go.mod │ ├── go.sum │ ├── metadata.yaml │ ├── providers.go │ └── providers_test.go └── versions.yaml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .checkapi.yaml ================================================ ignored_paths: - confmap/doc_test.go excluded_files: - example_*.go - "*_test.go" unkeyed_literal_initialization: enabled: true limit: 6 ================================================ FILE: .chloggen/README.md ================================================ ### Changelog folder This repo uses `chloggen` to manage its changelog files. You can find the source code for the tool [here](https://github.com/open-telemetry/opentelemetry-go-build-tools/tree/main/chloggen). Here is a quick explanation of the `config.yaml` file for chloggen: ```yaml # The directory that stores individual changelog entries. # Each entry is stored in a dedicated yaml file. # - 'chloggen new' will copy the 'template_yaml' to this directory as a new entry file. # - 'chloggen validate' will validate that all entry files are valid. # - 'chloggen update' will read and delete all entry files in this directory, and update 'changelog_md'. # Specify as relative path from root of repo. # (Optional) Default: .chloggen entries_dir: .chloggen # This file is used as the input for individual changelog entries. # Specify as relative path from root of repo. # (Optional) Default: .chloggen/TEMPLATE.yaml template_yaml: .chloggen/TEMPLATE.yaml summary_template: .chloggen/summary.tmpl # The CHANGELOG file or files to which 'chloggen update' will write new entries # (Optional) Default filename: CHANGELOG.md change_logs: user: CHANGELOG.md api: CHANGELOG-API.md # The default change_log or change_logs to which an entry should be added. # If 'change_logs' is specified in this file, and no value is specified for 'default_change_logs', # then 'change_logs' MUST be specified in every entry file. default_change_logs: [user] ``` ================================================ FILE: .chloggen/TEMPLATE.yaml ================================================ # Use this changelog template to create an entry for release notes. # One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' change_type: # The name of the component, or a single word describing the area of concern, (e.g. receiver/otlp) component: # A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). note: # One or more tracking issues or pull requests related to the change issues: [] # (Optional) One or more lines of additional information to render under the primary note. # These lines will be padded with 2 spaces and then inserted directly into the document. # Use pipe (|) for multiline entries. subtext: # Optional: The change log or logs in which this entry should be included. # e.g. '[user]' or '[user, api]' # Include 'user' if the change is relevant to end users. # Include 'api' if there is a change to a library API. # Default: '[user]' change_logs: [] ================================================ FILE: .chloggen/aix_tier3.yaml ================================================ # Use this changelog template to create an entry for release notes. # One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' change_type: enhancement # The name of the component, or a single word describing the area of concern, (e.g. receiver/otlp) component: all # A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). note: Move aix/ppc64 to tier 3 support # One or more tracking issues or pull requests related to the change issues: [13380] # (Optional) One or more lines of additional information to render under the primary note. # These lines will be padded with 2 spaces and then inserted directly into the document. # Use pipe (|) for multiline entries. subtext: # Optional: The change log or logs in which this entry should be included. # e.g. '[user]' or '[user, api]' # Include 'user' if the change is relevant to end users. # Include 'api' if there is a change to a library API. # Default: '[user]' change_logs: [] ================================================ FILE: .chloggen/alpha-profiles.yaml ================================================ # Use this changelog template to create an entry for release notes. # One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' change_type: enhancement # The name of the component, or a single word describing the area of concern, (e.g. receiver/otlp) component: all # A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). note: Upgrade the profiles stability status to alpha # One or more tracking issues or pull requests related to the change issues: [14817] # (Optional) One or more lines of additional information to render under the primary note. # These lines will be padded with 2 spaces and then inserted directly into the document. # Use pipe (|) for multiline entries. subtext: | The following components have their profiles status upgraded from development to alpha: * pdata/pprofile * connector/forward * exporter/debug * receiver/nop * exporter/nop * exporter/otlp_grpc * exporter/otlp_http # Optional: The change log or logs in which this entry should be included. # e.g. '[user]' or '[user, api]' # Include 'user' if the change is relevant to end users. # Include 'api' if there is a change to a library API. # Default: '[user]' change_logs: [] ================================================ FILE: .chloggen/config.yaml ================================================ change_logs: api: CHANGELOG-API.md user: CHANGELOG.md default_change_logs: - user entries_dir: .chloggen template_yaml: .chloggen/TEMPLATE.yaml summary_template: .chloggen/summary.tmpl components: - all - cmd/builder - cmd/mdatagen - connector/forward - connector/sample - consumer/consumererror/xconsumererror - consumer/xconsumer - docs/rfcs - exporter/debug - exporter/nop - exporter/otlp_grpc - exporter/otlp_http - extension/memory_limiter - extension/zpages - pdata/pprofile - pkg/client - pkg/component - pkg/component/componentstatus - pkg/component/componenttest - pkg/config/configauth - pkg/config/configcompression - pkg/config/configgrpc - pkg/config/confighttp/xconfighttp - pkg/config/configmiddleware - pkg/config/confignet - pkg/config/configopaque - pkg/config/configretry - pkg/config/configtelemetry - pkg/config/configtls - pkg/confighttp - pkg/configoptional - pkg/confmap - pkg/connector - pkg/connector/connectortest - pkg/consumer - pkg/consumer/consumererror - pkg/consumer/consumertest - pkg/exporter - pkg/exporter/exportertest - pkg/exporterhelper - pkg/extension - pkg/extension/extensiontest - pkg/extensionauth - pkg/extensionauth/extensionauthtest - pkg/extensioncapabilities - pkg/extensionmiddleware - pkg/extensionmiddleware/extensionmiddlewaretest - pkg/featuregate - pkg/filter - pkg/otelcol - pkg/otelcol/otelcoltest - pkg/pdata - pkg/pipeline - pkg/processor - pkg/processor/processortest - pkg/processorhelper - pkg/queuebatch - pkg/receiver - pkg/receiver/receivertest - pkg/receiverhelper - pkg/scraper - pkg/scraper/scrapertest - pkg/scraperhelper - pkg/service - pkg/service/hostcapabilities - pkg/service/telemetry/telemetrytest - pkg/xconfmap - pkg/xconnector - pkg/xexporter - pkg/xexporterhelper - pkg/xextension - pkg/xextension/storage - pkg/xpdata - pkg/xpipeline - pkg/xprocessor - pkg/xprocessorhelper - pkg/xreceiver - pkg/xscraper - pkg/xscraperhelper - processor/batch - processor/memory_limiter - processor/sample - provider/env - provider/file - provider/http - provider/https - provider/yaml - receiver/nop - receiver/otlp - receiver/sample - receiver/sample - receiver/sampleentity - scraper/sample - service/graph ================================================ FILE: .chloggen/fix-mdatagen-entity-builder.yaml ================================================ change_type: bug_fix component: cmd/mdatagen note: Fix entity code generation so `extra_attributes` are emitted as resource attributes instead of entity descriptive attributes. issues: [14778] ================================================ FILE: .chloggen/mdatagen_fix_reporoot.yaml ================================================ # Use this changelog template to create an entry for release notes. # One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' change_type: bug_fix # The name of the component, or a single word describing the area of concern, (e.g. receiver/otlp) component: cmd/mdatagen # A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). note: Fix RootPackage to use go module root instead of git repo root # One or more tracking issues or pull requests related to the change issues: [14801] # (Optional) One or more lines of additional information to render under the primary note. # These lines will be padded with 2 spaces and then inserted directly into the document. # Use pipe (|) for multiline entries. subtext: Fixes issue with running mdatagen when the git repository root is different from the go module root. # Optional: The change log or logs in which this entry should be included. # e.g. '[user]' or '[user, api]' # Include 'user' if the change is relevant to end users. # Include 'api' if there is a change to a library API. # Default: '[user]' change_logs: [api] ================================================ FILE: .chloggen/remove-resource-constant-labels.yaml ================================================ change_type: breaking component: pkg/service note: Remove `service_name`, `service_instance_id`, and `service_version` as constant labels on every internal metric datapoint. These attributes are already present in `target_info` and were being duplicated on each series for OpenCensus backwards compatibility. issues: [14811] subtext: | Previously, the collector stamped every internal metric series (e.g. `otelcol_process_runtime_heap_alloc_bytes`) with `service_name`, `service_instance_id`, and `service_version` labels to match the old OpenCensus behavior. These attributes are now only present in the `target_info` metric, which is the correct Prometheus/OTel convention. Users who filter or group by these labels on individual metrics will need to update their queries to use `target_info` joins instead. change_logs: [user] ================================================ FILE: .chloggen/summary.tmpl ================================================ {{- define "entry" -}} - `{{ .Component }}`: {{ .Note }} ( {{- range $i, $issue := .Issues }} {{- if $i }}, {{ end -}} #{{ $issue }} {{- end -}} ) {{- if .SubText }} {{ .SubText | indent 2 }} {{- end }} {{- end }} ## {{ .Version }} {{- if .BreakingChanges }} ### 🛑 Breaking changes 🛑 {{- range $i, $change := .BreakingChanges }} {{- if eq $i 0}} {{end}} {{ template "entry" $change }} {{- end }} {{- end }} {{- if .Deprecations }} ### 🚩 Deprecations 🚩 {{- range $i, $change := .Deprecations }} {{- if eq $i 0}} {{end}} {{ template "entry" $change }} {{- end }} {{- end }} {{- if .NewComponents }} ### 🚀 New components 🚀 {{- range $i, $change := .NewComponents }} {{- if eq $i 0}} {{end}} {{ template "entry" $change }} {{- end }} {{- end }} {{- if .Enhancements }} ### 💡 Enhancements 💡 {{- range $i, $change := .Enhancements }} {{- if eq $i 0}} {{end}} {{ template "entry" $change }} {{- end }} {{- end }} {{- if .BugFixes }} ### 🧰 Bug fixes 🧰 {{- range $i, $change := .BugFixes }} {{- if eq $i 0}} {{end}} {{ template "entry" $change }} {{- end }} {{- end }} ================================================ FILE: .codecov.yml ================================================ codecov: branch: main # only use the latest copy on main branch strict_yaml_branch: main coverage: precision: 2 round: down range: "80...100" status: project: default: enabled: yes target: 90% patch: default: enabled: yes target: 95% ignore: - "pdata/internal/data/protogen/**/*" - "**/*.pb.go" - "cmd/mdatagen/third_party/**/*" ================================================ FILE: .gitattributes ================================================ # This file is documented at https://git-scm.com/docs/gitattributes. # Linguist-specific attributes are documented at # https://github.com/github/linguist. go.sum linguist-generated=true # Avoid git status and other tools (lint, fmt) reporting whitespace differences # on Windows machines by ensuring that `lf` on text files are not converted to `crlf`. * text=auto eol=lf ================================================ FILE: .github/ALLOWLIST ================================================ # Code generated by githubgen. DO NOT EDIT. ##################################################### # # List of components # waiting on owners to be assigned # ##################################################### # # Learn about CODEOWNERS file format: # https://help.github.com/en/articles/about-code-owners # ## # NOTE: New components MUST have one or more codeowners. Add codeowners to the component metadata.yaml and run make gengithub ## ## COMMON & SHARED components internal/common ## DEPRECATED components # Start deprecated components list # End deprecated components list ## UNMAINTAINED components # Start unmaintained components list # End unmaintained components list ================================================ FILE: .github/CODEOWNERS ================================================ # Code generated by githubgen. DO NOT EDIT. ##################################################### # # List of codeowners # ##################################################### # # Learn about CODEOWNERS file format: # https://help.github.com/en/articles/about-code-owners # * @open-telemetry/collector-approvers # Files owned by collector-releases-approvers .github/workflows/prepare-release.yml @open-telemetry/collector-approvers @open-telemetry/collector-releases-approvers .github/workflows/sourcecode-release.yml @open-telemetry/collector-approvers @open-telemetry/collector-releases-approvers .github/workflows/scripts/release-*.sh @open-telemetry/collector-approvers @open-telemetry/collector-releases-approvers # Start components list cmd/builder/ @open-telemetry/collector-approvers @ArthurSens @dmathieu cmd/mdatagen/ @open-telemetry/collector-approvers @dmitryax cmd/mdatagen/internal/sampleconnector/ @open-telemetry/collector-approvers cmd/mdatagen/internal/sampleentityreceiver/ @open-telemetry/collector-approvers @dmitryax cmd/mdatagen/internal/samplefactoryreceiver/ @open-telemetry/collector-approvers @dmitryax cmd/mdatagen/internal/sampleprocessor/ @open-telemetry/collector-approvers cmd/mdatagen/internal/samplereceiver/ @open-telemetry/collector-approvers @dmitryax cmd/mdatagen/internal/samplescraper/ @open-telemetry/collector-approvers @dmitryax config/configauth/ @open-telemetry/collector-approvers config/configcompression/ @open-telemetry/collector-approvers config/configgrpc/ @open-telemetry/collector-approvers config/confighttp/ @open-telemetry/collector-approvers config/configmiddleware/ @open-telemetry/collector-approvers config/confignet/ @open-telemetry/collector-approvers config/configopaque/ @open-telemetry/collector-approvers config/configoptional/ @open-telemetry/collector-approvers config/configretry/ @open-telemetry/collector-approvers config/configtelemetry/ @open-telemetry/collector-approvers config/configtls/ @open-telemetry/collector-approvers confmap/ @open-telemetry/collector-approvers @mx-psi @evan-bradley confmap/provider/envprovider/ @open-telemetry/collector-approvers confmap/provider/fileprovider/ @open-telemetry/collector-approvers confmap/provider/httpprovider/ @open-telemetry/collector-approvers confmap/provider/httpsprovider/ @open-telemetry/collector-approvers confmap/provider/yamlprovider/ @open-telemetry/collector-approvers connector/forwardconnector/ @open-telemetry/collector-approvers connector/xconnector/ @open-telemetry/collector-approvers @mx-psi @dmathieu consumer/consumererror/xconsumererror/ @open-telemetry/collector-approvers consumer/xconsumer/ @open-telemetry/collector-approvers @mx-psi @dmathieu docs/rfcs/ @open-telemetry/collector-approvers @codeboten @bogdandrutu @dmitryax @mx-psi exporter/debugexporter/ @open-telemetry/collector-approvers @andrzej-stencel exporter/exporterhelper/ @open-telemetry/collector-approvers @bogdandrutu @dmitryax exporter/exporterhelper/internal/queuebatch/ @open-telemetry/collector-approvers exporter/exporterhelper/xexporterhelper/ @open-telemetry/collector-approvers @mx-psi @dmathieu exporter/nopexporter/ @open-telemetry/collector-approvers @evan-bradley exporter/otlpexporter/ @open-telemetry/collector-approvers exporter/otlphttpexporter/ @open-telemetry/collector-approvers exporter/xexporter/ @open-telemetry/collector-approvers @mx-psi @dmathieu extension/memorylimiterextension/ @open-telemetry/collector-approvers extension/xextension/ @open-telemetry/collector-approvers extension/xextension/storage/ @open-telemetry/collector-approvers @swiatekm extension/zpagesextension/ @open-telemetry/collector-approvers otelcol/ @open-telemetry/collector-approvers pdata/ @open-telemetry/collector-approvers @bogdandrutu @dmitryax pdata/pprofile/ @open-telemetry/collector-approvers @mx-psi @dmathieu pdata/xpdata/ @open-telemetry/collector-approvers processor/batchprocessor/ @open-telemetry/collector-approvers processor/memorylimiterprocessor/ @open-telemetry/collector-approvers processor/processorhelper/ @open-telemetry/collector-approvers processor/xprocessor/ @open-telemetry/collector-approvers @mx-psi @dmathieu receiver/nopreceiver/ @open-telemetry/collector-approvers @evan-bradley receiver/otlpreceiver/ @open-telemetry/collector-approvers receiver/receiverhelper/ @open-telemetry/collector-approvers receiver/xreceiver/ @open-telemetry/collector-approvers @mx-psi @dmathieu scraper/ @open-telemetry/collector-approvers scraper/xscraper @open-telemetry/collector-approvers scraper/scraperhelper/ @open-telemetry/collector-approvers scraper/scraperhelper/xscraperhelper @open-telemetry/collector-approvers service/ @open-telemetry/collector-approvers service/internal/graph/ @open-telemetry/collector-approvers # End components list ##################################################### # # List of distribution maintainers # ##################################################### # Start distribution list reports/distributions/core.yaml @open-telemetry/collector-approvers reports/distributions/contrib.yaml @open-telemetry/collector-approvers reports/distributions/k8s.yaml @open-telemetry/collector-approvers reports/distributions/otlp.yaml @open-telemetry/collector-approvers # End distribution list ##################################################### # ## UNMAINTAINED components # ##################################################### # Start unmaintained components list # End unmaintained components list ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.yaml ================================================ name: Bug report description: Create a report to help us improve labels: ["bug"] body: - type: markdown attributes: value: | Thanks for taking the time to fill out this bug report! Please make sure to fill out the entire form below, providing as much context as you can in order to help us triage and track down your bug as quickly as possible. Before filing a bug, please be sure you have searched through [existing bugs](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Abug) to see if an existing issue covers your bug. - type: dropdown id: component attributes: label: Component(s) description: Which component(s) does your bug report concern? multiple: true options: # NOTE: The list below is autogenerated using `make generate-gh-issue-templates` # Do not manually edit it. # Start components list - cmd/builder - cmd/mdatagen - cmd/mdatagen/internal/sampleconnector - cmd/mdatagen/internal/samplefactoryreceiver - cmd/mdatagen/internal/sampleprocessor - cmd/mdatagen/internal/samplereceiver - cmd/mdatagen/internal/samplescraper - config/configauth - config/configcompression - config/configgrpc - config/confighttp - config/configmiddleware - config/confignet - config/configopaque - config/configoptional - config/configretry - config/configtelemetry - config/configtls - confmap - confmap/provider/envprovider - confmap/provider/fileprovider - confmap/provider/httpprovider - confmap/provider/httpsprovider - confmap/provider/yamlprovider - connector/forward - connector/x - consumer/consumererror/xconsumererror - consumer/xconsumer - docs/rfcs - exporter/debug - exporter/exporterhelper - exporter/exporterhelper/internal/queuebatch - exporter/exporterhelper/xexporterhelper - exporter/nop - exporter/otlp - exporter/otlp_http - exporter/x - extension/memorylimiter - extension/x - extension/x/storage - extension/zpages - otelcol - pdata - pdata/pprofile - pdata/xpdata - processor/batch - processor/memorylimiter - processor/processorhelper - processor/x - receiver/nop - receiver/otlp - receiver/receiverhelper - receiver/x - scraper - scraper/scraperhelper - scraper/scraperhelper/xscraperhelper - scraper/xscraper - service - service/internal/graph # End components list - type: textarea attributes: label: What happened? description: Please provide as much detail as you reasonably can. value: | **Describe the bug** **Steps to reproduce** **What did you expect to see?** **What did you see instead?** validations: required: true - type: input attributes: label: Collector version description: What version did you use? (e.g., `v0.4.0`, `1eb551b`, etc) validations: required: true - type: textarea attributes: label: Environment information description: Please provide any additional information about your installation. value: | ## Environment OS: (e.g., "Ubuntu 20.04") Compiler(if manually compiled): (e.g., "go 14.2") - type: textarea attributes: label: OpenTelemetry Collector configuration description: Please provide the configuration you are using (e.g. the YAML config file). placeholder: | # Empty Collector config receivers: exporters: processors: extensions: service: pipelines: traces: receivers: [] exporters: [] processors: [] metrics: receivers: [] exporters: [] processors: [] logs: receivers: [] exporters: [] processors: [] render: yaml - type: textarea attributes: label: Log output description: | Please copy and paste any relevant log output. render: shell - type: textarea attributes: label: Additional context description: Any additional information you think may be relevant to this issue. - type: dropdown attributes: label: Tip description: This element is static, used to render a helpful sub-heading for end-users and community members to help prioritize issues. Please leave as is. options: - [React](https://github.blog/news-insights/product-news/add-reactions-to-pull-requests-issues-and-comments/) with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding `+1` or `me too`, to help us triage it. Learn more [here](https://opentelemetry.io/community/end-user/issue-participation/). default: 0 ================================================ FILE: .github/ISSUE_TEMPLATE/component-graduation.md ================================================ --- name: Component Graduation about: Graduate a component from beta to stable title: 'Graduate component X to stable' labels: 'graduation' assignees: '' --- This issue requests the graduation of a component to stable. Please review the [Component Graduation to Stable](https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#component-graduation-to-stable) documentation for full details. ## Component Information - **Component name**: - **Component type**: - **Repository**: ## Signal Requirements - [ ] All supported signals for signal types that are stable in the OpenTelemetry specification are at beta stability or higher - [ ] At least one signal is at stable stability ## Code Owner Requirements - [ ] The component has at least three active code owners - [ ] Within the 60 days prior to this request, the code owners have reviewed and replied to at least 80% of the issues and pull requests opened against the component List the current code owners: 1. 2. 3. ## Technical Requirements - [ ] The component meets all [testing requirements](https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#testing-requirements) for stable components - [ ] The component meets all [documentation requirements](https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#documentation-requirements) for stable components - [ ] The component meets all [observability requirements](https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#observability-requirements) for stable components - [ ] The component follows the [coding guidelines](https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/coding-guidelines.md), including naming conventions Please provide links to evidence: - **Test coverage report**: - **Benchmark results**: - **Documentation**: ## Adoption Evidence The component must have evidence of real-world adoption. Provide at least one of the following: ### Option 1: Public Adopter Testimonials At least two organizations have publicly stated they use the component in production. - [ ] Adopter 1: - [ ] Adopter 2: ### Option 2: Private Attestation If adopters cannot be named publicly, provide private attestation to the assigned maintainer. - [ ] Private attestation provided to maintainer The attestation must include a general description of the scale of usage (e.g., "processing millions of spans per day"). --- ## For Maintainers A maintainer will be assigned on a rotating basis to verify this graduation request. - [ ] Maintainer assigned: - [ ] All requirements verified - [ ] Adoption evidence verified as credible Once verified, the code owners should open a PR to update the component's stability level. **Tip**: [React](https://github.blog/news-insights/product-news/add-reactions-to-pull-requests-issues-and-comments/) with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding `+1` or `me too`, to help us triage it. Learn more [here](https://opentelemetry.io/community/end-user/issue-participation/). ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.yaml ================================================ name: Feature request description: Suggest an idea for this project labels: ["feature request"] body: - type: dropdown id: component attributes: label: Component(s) description: Which component(s) does your feature request concern? multiple: true options: # NOTE: The list below is autogenerated using `make generate-gh-issue-templates` # Do not manually edit it. # Start components list - cmd/builder - cmd/mdatagen - cmd/mdatagen/internal/sampleconnector - cmd/mdatagen/internal/samplefactoryreceiver - cmd/mdatagen/internal/sampleprocessor - cmd/mdatagen/internal/samplereceiver - cmd/mdatagen/internal/samplescraper - config/configauth - config/configcompression - config/configgrpc - config/confighttp - config/configmiddleware - config/confignet - config/configopaque - config/configoptional - config/configretry - config/configtelemetry - config/configtls - confmap - confmap/provider/envprovider - confmap/provider/fileprovider - confmap/provider/httpprovider - confmap/provider/httpsprovider - confmap/provider/yamlprovider - connector/forward - connector/x - consumer/consumererror/xconsumererror - consumer/xconsumer - docs/rfcs - exporter/debug - exporter/exporterhelper - exporter/exporterhelper/internal/queuebatch - exporter/exporterhelper/xexporterhelper - exporter/nop - exporter/otlp_grpc - exporter/otlp_http - exporter/x - extension/memorylimiter - extension/x - extension/x/storage - extension/zpages - otelcol - pdata - pdata/pprofile - pdata/xpdata - processor/batch - processor/memorylimiter - processor/processorhelper - processor/x - receiver/nop - receiver/otlp - receiver/receiverhelper - receiver/x - scraper - scraper/scraperhelper - scraper/scraperhelper/xscraperhelper - scraper/xscraper - service - service/internal/graph # End components list - type: textarea attributes: label: Is your feature request related to a problem? Please describe. description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]. We are currently preparing for the upcoming 1.0 GA release. Feature requests that are not aligned with the current roadmap and are not aimed at stabilizing and preparing the Collector for the release will not be prioritized. validations: required: true - type: textarea attributes: label: Describe the solution you'd like description: A clear and concise description of what you want to happen. validations: required: true - type: textarea attributes: label: Describe alternatives you've considered description: A clear and concise description of any alternative solutions or features you've considered. - type: textarea attributes: label: Additional context description: Add any other context or screenshots about the feature request here. - type: dropdown attributes: label: Tip description: This element is static, used to render a helpful sub-heading for end-users and community members to help prioritize issues. Please leave as is. options: - [React](https://github.blog/news-insights/product-news/add-reactions-to-pull-requests-issues-and-comments/) with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding `+1` or `me too`, to help us triage it. Learn more [here](https://opentelemetry.io/community/end-user/issue-participation/). default: 0 ================================================ FILE: .github/ISSUE_TEMPLATE/other.yaml ================================================ name: Other issue description: Create a new issue to help us improve the collector body: - type: dropdown id: component attributes: label: Component(s) description: Which component(s) does your issue concern? multiple: true options: # NOTE: The list below is autogenerated using `make generate-gh-issue-templates` # Do not manually edit it. # Start components list - cmd/builder - cmd/mdatagen - cmd/mdatagen/internal/sampleconnector - cmd/mdatagen/internal/samplefactoryreceiver - cmd/mdatagen/internal/sampleprocessor - cmd/mdatagen/internal/samplereceiver - cmd/mdatagen/internal/samplescraper - config/configauth - config/configcompression - config/configgrpc - config/confighttp - config/configmiddleware - config/confignet - config/configopaque - config/configoptional - config/configretry - config/configtelemetry - config/configtls - confmap - confmap/provider/envprovider - confmap/provider/fileprovider - confmap/provider/httpprovider - confmap/provider/httpsprovider - confmap/provider/yamlprovider - connector/forward - connector/x - consumer/consumererror/xconsumererror - consumer/xconsumer - docs/rfcs - exporter/debug - exporter/exporterhelper - exporter/exporterhelper/internal/queuebatch - exporter/exporterhelper/xexporterhelper - exporter/nop - exporter/otlp_grpc - exporter/otlp_http - exporter/x - extension/memorylimiter - extension/x - extension/x/storage - extension/zpages - otelcol - pdata - pdata/pprofile - pdata/xpdata - processor/batch - processor/memorylimiter - processor/processorhelper - processor/x - receiver/nop - receiver/otlp - receiver/receiverhelper - receiver/x - scraper - scraper/scraperhelper - scraper/scraperhelper/xscraperhelper - scraper/xscraper - service - service/internal/graph # End components list - type: textarea attributes: label: Describe the issue you're reporting description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] validations: required: true - type: dropdown attributes: label: Tip description: This element is static, used to render a helpful sub-heading for end-users and community members to help prioritize issues. Please leave as is. options: - [React](https://github.blog/news-insights/product-news/add-reactions-to-pull-requests-issues-and-comments/) with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding `+1` or `me too`, to help us triage it. Learn more [here](https://opentelemetry.io/community/end-user/issue-participation/). default: 0 ================================================ FILE: .github/ISSUE_TEMPLATE/stabilization.md ================================================ --- name: Module stabilization about: Stabilize a module before a 1.0 release title: 'Stabilize module X' labels: 'stabilization' assignees: '' --- Before stabilizing a module, an approver or maintainer must make sure that the following criteria have been met for at least two successive minor version releases (regardless of when this issue was opened): - [ ] No open issues or PRs in the module that would require breaking changes - [ ] No TODOs in the module code that would require breaking changes - [ ] No deprecated symbols in the module - [ ] No symbols marked as experimental in the module - [ ] The module follows the [Coding guidelines](https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/coding-guidelines.md) Please also make sure to publicly announce our intent to stabilize the module on: - [ ] The #otel-collector CNCF Slack Channel - [ ] The #opentelemetry CNCF Slack channel - [ ] A Collector SIG meeting (if unable to attend, just add to the agenda) To help other people verify the above criteria, please link to the announcement and other links used to complete the above in a comment on this issue. Once all criteria are met, close this issue by moving this module to the `stable` module set. **Tip**: [React](https://github.blog/news-insights/product-news/add-reactions-to-pull-requests-issues-and-comments/) with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding `+1` or `me too`, to help us triage it. Learn more [here](https://opentelemetry.io/community/end-user/issue-participation/). ================================================ FILE: .github/ISSUE_TEMPLATE/vote.md ================================================ --- name: Vote about: Vote to make a decision related to an RFC title: '[Vote] RFC #XXXX:' labels: "rfc:vote-needed" assignees: '' --- A [vote](https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/rfcs/README.md#voting) has been called for RFC #XXXX following the RFC process. ### Stakeholders Any person in the community may vote. Votes of the stakeholders are binding. Stakeholders are encouraged to consider the views from the wider community when casting their vote. As defined in the RFC, there are N stakeholders for this RFC divided as follows: | Stakeholders | As defined on date | Number of people | |-------------------------------------|--------------------|------------------| | @open-telemetry/collector-approvers | yyyy-mm-dd | N | For any stakeholder team, we consider the people that were part of the team at the time the vote is called. ### Voting options | Option | Description | # Votes (stakeholders) | # Votes (total) | |--------|-------------|------------------------|-----------------| | Option 1 | Description of the option | 0 | 0 | ### Result The vote is in progress. A minimum of X votes is required to select an option. ### Vote process Please **leave a comment** with your vote and any additional context you would like to provide. Start your comment with "I vote for **Option N**." and then provide any additional context. ### Related links Include here any links to the RFC, other PRs and resources that help make an informed vote. ### Checklists When starting the vote: - [ ] Announce the vote in the #otel-collector-dev CNCF Slack channel. - [ ] Add an entry to announce the vote in the next Collector SIG meeting. For closing the vote: - [ ] At least five business days have passed since the vote was announced. - [ ] At least one-third of the stakeholders have voted. - [ ] The "Voting options" and "Result" sections have been updated reflecting the votes casted. - [ ] The RFC has been updated reflecting the votes casted. **Tip**: [React](https://github.blog/news-insights/product-news/add-reactions-to-pull-requests-issues-and-comments/) with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding `+1` or `me too`, to help us triage it. Learn more [here](https://opentelemetry.io/community/end-user/issue-participation/). ================================================ FILE: .github/actionlint.yaml ================================================ self-hosted-runner: labels: [] config-variables: null paths: .github/workflows/**/*.{yml,yaml}: ignore: # SC1070: Suppressing because scripts intentionally contain valid but unusual characters, # Escape characters like `\o` used purposefully, and tested for clarity - '.*shellcheck reported issue in this script: SC1070:.+' # SC1133: ShellCheck suggests optimizing conditional expressions, but these scripts # operate correctly and readability is prioritized in real-world use. This ensures familiarity for contributors. - '.*shellcheck reported issue in this script: SC1133:.+' # SC2086: Ignored because word splitting is intentional in commands like `git diff`, # where simple, predictable paths are passed as arguments. No unintended globbing occurs in this context. - '.*shellcheck reported issue in this script: SC2086:.+' # SC2046: Suppressed because word splitting is desired and necessary in certain scenarios, # PR_HEAD is set by GitHub Actions and paths are fixed/controlled. - '.*shellcheck reported issue in this script: SC2046:.+' # SC2059: Format strings in `printf` are deliberately designed and controlled for specific outputs. # ShellCheck’s safeguard warning is appreciated but not critical in these cases. - '.*shellcheck reported issue in this script: SC2059:.+' # SC2236: Both `! -z` and `-n` achieve the same result, and while `-n` is idiomatic. (Just a style suggestion) # suppressing this warning allows scripts to remain consistent with existing standards. - '.*shellcheck reported issue in this script: SC2236:.+' # SC1001: Escaped characters (like `\o`) are deliberate in certain scripts for expected functionality. # ShellCheck’s flagging of these characters as potential issues isn’t applicable to this use case. - '.*shellcheck reported issue in this script: SC1001:.+' # SC2129: Individual redirections are chosen for simplicity and clarity in the workflows. # combining them is technically efficient, the current approach ensures more readable scripts. - '.*shellcheck reported issue in this script: SC2129:.+' # Runner warnings ignored because scripts are validated against specific configurations # and tested on GitHub Actions, ensuring compatibility. These warnings do not affect functionality. - '.*the runner of ".+" action is too old to run on GitHub Actions.+' ================================================ FILE: .github/lychee.toml ================================================ include_fragments = true accept = ["200..=299", "429"] exclude = [ "^http(s)?://localhost", "^http(s)?://example.com" ] ================================================ FILE: .github/pull_request_template.md ================================================ #### Description #### Link to tracking issue Fixes # #### Testing #### Documentation ================================================ FILE: .github/workflows/add-labels-and-owners.yml ================================================ name: 'Add labels and code owners to PR' on: pull_request_target: types: - opened - synchronize - ready_for_review permissions: read-all jobs: add-labels-and-owners: permissions: pull-requests: write runs-on: ubuntu-24.04 if: ${{ github.actor != 'dependabot[bot]' && github.actor != 'renovate[bot]' && github.repository_owner == 'open-telemetry' && github.event.pull_request.draft == false }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Run add-codeowners-to-pr.sh run: ./.github/workflows/scripts/add-labels-and-owners.sh env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} REPO: ${{ github.repository }} PR: ${{ github.event.number }} ================================================ FILE: .github/workflows/add-labels-command.yml ================================================ name: 'Add Labels' on: issue_comment: types: [created] permissions: read-all jobs: add-labels-command: if: ${{ !github.event.issue.pull_request && startsWith(github.event.comment.body, '/label') && github.repository_owner == 'open-telemetry' }} permissions: issues: write runs-on: ubuntu-24.04 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Run add-labels-command.sh run: ./.github/workflows/scripts/add-labels-command.sh env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ISSUE: ${{ github.event.issue.number }} COMMENT: ${{ github.event.comment.body }} SENDER: ${{ github.event.sender.login }} ================================================ FILE: .github/workflows/api-compatibility.yml ================================================ # This GitHub action is used to compare API state snapshots of Main # to Head of the PR in order to validate releases are not breaking # backwards compatibility. # # This GitHub action will fail if there are incompatible changes. # name: "Inform Incompatible PRs" on: pull_request: branches: - main permissions: read-all jobs: Check-Compatibility: runs-on: ubuntu-latest env: BASE_REF: ${{ github.base_ref }} HEAD_REF: ${{ github.head_ref }} steps: - name: Checkout-Main uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ github.base_ref }} path: ${{ github.base_ref }} - name: Checkout-HEAD uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: ${{ github.head_ref }} - name: Setup Go uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version: oldstable cache: false - name: Cache Go id: go-cache uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: | ~/go/bin ~/go/pkg/mod key: go-cache-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/go.sum') }} # Generate apidiff states of Main - name: Generate-States run: | cd $BASE_REF make apidiff-build # Compare apidiff states of Main with PR - name: Compare-States env: CI: true COMPARE_OPTS: -d "../${{ github.base_ref }}/internal/data/apidiff" run: | cd $HEAD_REF make apidiff-compare # Fail GitHub Action if there are incompatible changes - name: Check-States env: CI: true COMPARE_OPTS: -d "../${{ github.base_ref }}/internal/data/apidiff" -c run: | cd $HEAD_REF make apidiff-compare ================================================ FILE: .github/workflows/build-and-test-arm.yml ================================================ name: build-and-test-arm on: push: branches: [main] tags: - "v[0-9]+.[0-9]+.[0-9]+*" merge_group: types: [checks_requested] pull_request: env: TEST_RESULTS: testbed/tests/results/junit/results.xml # Make sure to exit early if cache segment download times out after 2 minutes. # We limit cache download as a whole to 5 minutes. SEGMENT_DOWNLOAD_TIMEOUT_MINS: 2 permissions: read-all # Do not cancel this workflow on main. See https://github.com/open-telemetry/opentelemetry-collector-contrib/pull/16616 concurrency: group: ${{ github.workflow }}-${{ github.ref_name }} cancel-in-progress: true jobs: arm-unittest-matrix: strategy: matrix: os: [ubuntu-22.04-arm, macos-14] if: ${{ github.actor != 'dependabot[bot]' && (contains(github.event.pull_request.labels.*.name, 'Run ARM') || github.event_name == 'push' || github.event_name == 'merge_group') }} runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version: oldstable cache: false - name: Cache Go id: go-cache timeout-minutes: 5 uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: | ~/go/bin ~/go/pkg/mod key: go-cache-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/go.sum') }} - name: Install dependencies if: steps.go-cache.outputs.cache-hit != 'true' run: make gomoddownload - name: Run Unit Tests run: make -j4 gotest arm-unittest: if: ${{ github.actor != 'dependabot[bot]' && (contains(github.event.pull_request.labels.*.name, 'Run ARM') || github.event_name == 'push' || github.event_name == 'merge_group') }} runs-on: ubuntu-latest needs: [arm-unittest-matrix] steps: - name: Print result run: echo ${{ needs.arm-unittest-matrix.result }} - name: Interpret result run: | if [[ success == ${{ needs.arm-unittest-matrix.result }} ]] then echo "All matrix jobs passed!" else echo "One or more matrix jobs failed." false fi ================================================ FILE: .github/workflows/build-and-test-windows.yaml ================================================ name: build-and-test-windows on: push: branches: [main] tags: - "v[0-9]+.[0-9]+.[0-9]+*" merge_group: types: [checks_requested] pull_request: concurrency: group: ${{ github.workflow }}-${{ github.ref_name }} cancel-in-progress: true permissions: read-all jobs: windows-unittest: strategy: fail-fast: false matrix: os: [windows-2022, windows-2025, windows-11-arm] runs-on: ${{ matrix.os }} steps: - name: Checkout Repo uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup Go uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version: oldstable cache: false - name: Cache Go uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 env: cache-name: cache-go-modules with: path: | ~\go\pkg\mod ~\AppData\Local\go-build key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - name: Ensure required ports in the dynamic range are available run: | & ${{ github.workspace }}\.github\workflows\scripts\win-required-ports.ps1 - name: Run Unit Tests run: make gotest windows-service-test: strategy: fail-fast: false matrix: os: [windows-2022, windows-2025, windows-11-arm] runs-on: ${{ matrix.os }} steps: - name: Checkout Repo uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup Go uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version: oldstable cache: false - name: Cache Go uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 env: cache-name: cache-go-modules with: path: | ~\go\pkg\mod ~\AppData\Local\go-build key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - name: Ensure required ports in the dynamic range are available run: | & ${{ github.workspace }}\.github\workflows\scripts\win-required-ports.ps1 - name: Make otelcorecol run: make otelcorecol - name: Install otelcorecol as a service run: | New-Service -Name "otelcorecol" -StartupType "Manual" -BinaryPathName "${PWD}\bin\otelcorecol_windows_$(go env GOARCH) --config ${PWD}\examples\local\otel-config.yaml" eventcreate.exe /t information /id 1 /l application /d "Creating event provider for 'otelcorecol'" /so otelcorecol - name: Test otelcorecol service working-directory: ${{ github.workspace }}/otelcol run: | go test -timeout 90s -run ^TestCollectorAsService$ -v -tags=win32service - name: Remove otelcorecol service if: always() run: | Remove-Service otelcorecol Remove-Item HKLM:\SYSTEM\CurrentControlSet\Services\EventLog\Application\otelcorecol ================================================ FILE: .github/workflows/build-and-test.yml ================================================ name: build-and-test on: push: branches: [main] tags: - "v[0-9]+.[0-9]+.[0-9]+*" merge_group: types: [checks_requested] pull_request: permissions: read-all concurrency: group: ${{ github.workflow }}-${{ github.ref_name }} cancel-in-progress: true jobs: setup-environment: runs-on: ubuntu-latest steps: - name: Checkout Repo uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup Go uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version: oldstable cache: false - name: Cache Go id: go-cache uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: | ~/go/bin ~/go/pkg/mod key: go-cache-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/go.sum') }} - name: Install dependencies if: steps.go-cache.outputs.cache-hit != 'true' run: make gomoddownload lint: runs-on: ubuntu-latest needs: [setup-environment] steps: - name: Checkout Repo uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup Go uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version: oldstable cache: false - name: Cache Go id: go-cache uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: | ~/go/bin ~/go/pkg/mod key: go-cache-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/go.sum') }} - name: golint run: make -j2 golint - name: goimpi run: make goimpi govulncheck: runs-on: ubuntu-latest timeout-minutes: 30 needs: [setup-environment] steps: - name: Checkout Repo uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup Go uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version: oldstable cache: false - name: Cache Go id: go-cache uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: | ~/go/bin ~/go/pkg/mod key: go-cache-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/go.sum') }} - name: Run `govulncheck` run: make govulncheck checks: runs-on: ubuntu-latest needs: [setup-environment] steps: - name: Checkout Repo uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup Go uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version: stable cache: false - name: Cache Go id: go-cache uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: | ~/go/bin ~/go/pkg/mod key: go-cache-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/go.sum') }} - name: Setup Node uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: "24" - name: checklicense run: make checklicense - name: misspell run: make misspell - name: checkdoc run: make checkdoc - name: markdownlint run: make markdownlint - name: checkapi run: make checkapi - name: Check for go mod dependency changes run: | make gotidy git diff --exit-code || (echo 'go.mod/go.sum deps changes detected, please run "make gotidy" and commit the changes in this PR.' && exit 1) - name: go:porto run: | make goporto git diff --exit-code || (echo 'Porto links are out of date, please run "make goporto" and commit the changes in this PR.' && exit 1) - name: go:generate run: | make gogenerate git diff --exit-code || (echo 'Generated code is out of date, please run "make gogenerate" and commit the changes in this PR.' && exit 1) - name: Generate proto files run: | make genproto git diff --exit-code || (echo 'Generated code is out of date, please run "make genproto" and commit the changes in this PR.' && exit 1) - name: Gen Pdata run: | make genpdata git diff --exit-code || (echo 'Generated code is out of date, please run "make genpdata" and commit the changes in this PR.' && exit 1) - name: Gen otelcorecol run: | make genotelcorecol git diff --exit-code || (echo 'Generated code is out of date, please run "make genotelcorecol" and commit the changes in this PR.' && exit 1) - name: Multimod verify run: make multimod-verify - name: crosslink run: | make crosslink git diff -s --exit-code || (echo 'Replace statements are out of date, please run "make crosslink" and commit the changes in this PR.' && exit 1) - name: generate-chloggen-components run: | make generate-chloggen-components git diff --exit-code || (echo '.chloggen/config.yaml is out of date, please run "make generate-chloggen-components" and commit the changes.' && exit 1) unittest-matrix: strategy: matrix: runner: [ubuntu-latest] go-version: ["stable", "oldstable"] runs-on: ${{ matrix.runner }} needs: [setup-environment] steps: - name: Checkout Repo uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup Go uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version: ${{ matrix.go-version }} cache: false - name: Cache Go id: go-cache uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: | ~/go/bin ~/go/pkg/mod key: go-cache-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/go.sum') }} - name: Cache Build uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: ~/.cache/go-build key: unittest-${{ runner.os }}-${{ matrix.runner }}-go-build-${{ matrix.go-version }}-${{ hashFiles('**/go.sum') }} - name: Run Unit Tests run: | make -j4 gotest-with-junit - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: test-results-${{ runner.os }}-${{ matrix.runner }}-${{ matrix.go-version }} path: internal/tools/testresults/ retention-days: 4 unittest: if: always() runs-on: ubuntu-latest needs: [setup-environment, unittest-matrix] steps: - name: Print result run: echo ${{ needs.unittest-matrix.result }} - name: Interpret result run: | if [[ success == ${{ needs.unittest-matrix.result }} ]] then echo "All matrix jobs passed!" else echo "One or more matrix jobs failed." false fi test-coverage: runs-on: ubuntu-latest needs: [setup-environment] steps: - name: Checkout Repo uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup Go uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version: oldstable cache: false - name: Cache Go id: go-cache uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: | ~/go/bin ~/go/pkg/mod key: go-cache-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/go.sum') }} - name: Cache Build uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: ~/.cache/go-build key: coverage-${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }} - name: Run Unit Tests With Coverage run: make gotest-with-cover - name: Upload coverage report uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # 5.5.2 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} cross-build-collector: needs: [setup-environment] runs-on: ubuntu-latest timeout-minutes: 10 strategy: fail-fast: false matrix: include: # Go 1.15 dropped support for 32-bit binaries # on macOS: https://go.dev/doc/go1.15 #- goos: darwin # goarch: 386 - goos: aix goarch: ppc64 - goos: darwin goarch: amd64 - goos: darwin goarch: arm64 - goos: js goarch: wasm - goos: linux goarch: 386 - goos: linux goarch: amd64 - goos: linux goarch: arm64 - goos: linux goarch: ppc64le - goos: linux goarch: riscv64 - goos: linux goarch: arm goarm: 7 - goos: linux goarch: s390x - goos: windows goarch: 386 - goos: windows goarch: amd64 - goos: windows goarch: arm64 steps: - name: Checkout Repo uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup Go uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version: oldstable cache: false - name: Cache Go id: go-cache uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: | ~/go/bin ~/go/pkg/mod key: go-cache-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/go.sum') }} - name: Build env: GOOS: ${{matrix.goos}} GOARCH: ${{matrix.goarch}} GOARM: ${{matrix.goarm}} run: | make otelcorecol ================================================ FILE: .github/workflows/builder-integration-test.yaml ================================================ name: Builder - Integration tests on: # on changes to the main branch touching the builder push: branches: [main] # on PRs touching the builder pull_request: branches: [main] # once a day at 6:17 AM UTC schedule: - cron: "17 6 * * *" # manual execution workflow_dispatch: merge_group: types: [checks_requested] concurrency: group: ${{ github.workflow }}-${{ github.ref_name }} cancel-in-progress: true permissions: read-all jobs: integration-test: name: Integration test runs-on: ubuntu-latest steps: - name: Checkout Repo uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup Go uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version: oldstable cache: false - name: Cache Go id: go-cache uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: | ~/go/bin ~/go/pkg/mod key: go-cache-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/go.sum') }} - name: Test run: make builder-integration-test ================================================ FILE: .github/workflows/builder-snapshot.yaml ================================================ name: Builder - Snapshot build on: push: branches: [main] # on PRs touching the builder pull_request: branches: [main] paths: - "cmd/builder/**" permissions: contents: read env: # renovate: datasource=github-tags depName=goreleaser-pro packageName=goreleaser/goreleaser-pro GORELEASER_PRO_VERSION: v2.11.1 jobs: snapshot: runs-on: ubuntu-24.04 if: ${{ github.repository_owner == 'open-telemetry' }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: .core - name: Pull the latest releases repo uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: opentelemetry-collector-releases repository: open-telemetry/opentelemetry-collector-releases - name: Copy release files run: cp -R ./opentelemetry-collector-releases/cmd/builder/. ./.core/cmd/builder/ - uses: sigstore/cosign-installer@ba7bc0a3fef59531c69a25acd34668d6d3fe6f22 # v4.1.0 - uses: anchore/sbom-action/download-syft@57aae528053a48a3f6235f2d9461b05fbcb7366d # v0.23.1 - uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0 with: platforms: amd64,arm64,ppc64le,s390x,riscv64 - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - name: Setup Go uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version: stable cache: false - name: Cache Go id: go-cache uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: | ~/go/bin ~/go/pkg/mod key: go-cache-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/go.sum') }} - name: Check GoReleaser uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29 # v7.0.0 with: distribution: goreleaser-pro version: ${{ env.GORELEASER_PRO_VERSION }} args: check --verbose -f .core/cmd/builder/.goreleaser.yaml env: GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Run GoReleaser uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29 # v7.0.0 with: distribution: goreleaser-pro version: ${{ env.GORELEASER_PRO_VERSION }} args: --snapshot --clean -f .core/cmd/builder/.goreleaser.yaml --skip sign env: GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} COSIGN_YES: false # Only create an issue if the workflows fails on push to main branch - name: File an issue if the workflow failed if: failure() && github.ref == 'refs/heads/main' run: | template=$(cat <<'END' [Link to job log](%s) END ) job_url="$(gh run view ${{ github.run_id }} -R ${{ github.repository }} --json jobs -q '.jobs[] | select(.name == "snapshot") | .url')" body="$(printf "$template" "$job_url")" gh issue create -R ${{ github.repository }} -t 'OCB snapshot workflow failed' -b "$body" -l 'ci-cd' -l 'area:builder' env: GH_TOKEN: ${{ github.token }} ================================================ FILE: .github/workflows/changelog.yml ================================================ # This action requires that any PR targeting the main branch should touch at # least one CHANGELOG file. If a CHANGELOG entry is not required, add the "Skip # Changelog" label to disable this action. name: changelog on: pull_request: types: [opened, ready_for_review, synchronize, reopened, labeled, unlabeled, edited] branches: - main merge_group: types: [checks_requested] concurrency: group: ${{ github.workflow }}-${{ github.ref_name }} cancel-in-progress: true permissions: read-all jobs: changelog: runs-on: ubuntu-latest if: ${{ github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'dependencies') && !contains(github.event.pull_request.labels.*.name, 'Skip Changelog') && !contains(github.event.pull_request.title, '[chore]') }} env: PR_HEAD: ${{ github.event.pull_request.head.sha }} steps: - name: Checkout Repo uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - name: Setup Go uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version: oldstable cache: false - name: Cache Go id: go-cache uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: | ~/go/bin ~/go/pkg/mod key: go-cache-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/go.sum') }} - name: Ensure no changes to the CHANGELOG.md or CHANGELOG-API.md run: | if [[ $(git diff --name-only $(git merge-base origin/main $PR_HEAD) $PR_HEAD ./CHANGELOG*.md) ]] then echo "CHANGELOG.md and CHANGELOG-API.md should not be directly modified." echo "Please add a .yaml file to the ./.chloggen/ directory." echo "See CONTRIBUTING.md for more details." echo "Alternately, add either \"[chore]\" to the title of the pull request or add the \"Skip Changelog\" label if this job should be skipped." false else echo "CHANGELOG.md and CHANGELOG-API.md were not modified." fi - name: Ensure ./.chloggen/*.yaml addition(s) run: | if [[ 1 -gt $(git diff --diff-filter=A --name-only $(git merge-base origin/main $PR_HEAD) $PR_HEAD ./.chloggen | grep -c \\.yaml) ]] then echo "No changelog entry was added to the ./.chloggen/ directory." echo "Please add a .yaml file to the ./.chloggen/ directory." echo "See CONTRIBUTING.md for more details." echo "Alternately, add either \"[chore]\" to the title of the pull request or add the \"Skip Changelog\" label if this job should be skipped." false else echo "A changelog entry was added to the ./.chloggen/ directory." fi - name: Validate ./.chloggen/*.yaml changes run: | make chlog-validate \ || { echo "New ./.chloggen/*.yaml file failed validation."; exit 1; } # In order to validate any links in the yaml file, render the config to markdown - name: Render .chloggen changelog entries run: make chlog-preview > changelog_preview.md - name: Link Checker id: lychee uses: lycheeverse/lychee-action@8646ba30535128ac92d33dfc9133794bfdd9b411 # v2.8.0 with: args: "--verbose --no-progress ./changelog_preview.md --config .github/lychee.toml" failIfEmpty: false ================================================ FILE: .github/workflows/check-codeowners.yaml ================================================ name: codeowners on: push: branches: [main] paths: - ".github/CODEOWNERS" - "**/metadata.yaml" tags: - "v[0-9]+.[0-9]+.[0-9]+*" pull_request_target: paths: - ".github/CODEOWNERS" - "**/metadata.yaml" types: - opened - synchronize - edited - reopened env: # Make sure to exit early if cache segment download times out after 2 minutes. # We limit cache download as a whole to 5 minutes. SEGMENT_DOWNLOAD_TIMEOUT_MINS: 2 # Do not cancel this workflow on main. See https://github.com/open-telemetry/opentelemetry-collector-contrib/pull/16616 concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true permissions: read-all jobs: check-codeowners: timeout-minutes: 30 runs-on: ubuntu-24.04 if: ${{ github.actor != 'dependabot[bot]' && github.repository == 'open-telemetry/opentelemetry-collector' }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6 id: go-setup with: go-version: oldstable cache-dependency-path: "**/*.sum" - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: ref: ${{github.event.pull_request.head.ref}} repository: ${{github.event.pull_request.head.repo.full_name}} path: pr - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 id: otelbot-token with: app-id: ${{ vars.OTELBOT_APP_ID }} private-key: ${{ secrets.OTELBOT_PRIVATE_KEY }} # NOTE: the make command below intentionally uses the Makefile from the # target branch, and not the PR checkout, since it runs with the # pull_request_target event and has elevated permissions. - name: Gen CODEOWNERS run: | GITHUB_TOKEN=${{ steps.otelbot-token.outputs.token }} GITHUBGEN_ARGS="-folder=./pr" make generate-codeowners git diff -s --exit-code || (echo 'Generated code is out of date, please run "make generate-codeowners" or apply this diff and commit the changes in this PR.' && git diff && exit 1) ================================================ FILE: .github/workflows/check-links.yaml ================================================ name: check-links on: push: branches: [main] pull_request: merge_group: types: [checks_requested] concurrency: group: ${{ github.workflow }}-${{ github.ref_name }} cancel-in-progress: true permissions: read-all jobs: changedfiles: name: changed files runs-on: ubuntu-latest env: PR_HEAD: ${{ github.event.pull_request.head.sha }} outputs: files: ${{ steps.changes.outputs.files }} steps: - name: Checkout Repo uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - name: Get changed files id: changes run: | files=$(git diff --name-only --diff-filter=ACMRTUXB $(git merge-base origin/main $PR_HEAD) $PR_HEAD | grep .md$ | xargs) if [ -z "$files" ] && git diff --name-only $(git merge-base origin/main $PR_HEAD) $PR_HEAD | grep -q "package.json"; then files="**/*.md" fi echo "files=$files" >> $GITHUB_OUTPUT check-links: runs-on: ubuntu-latest needs: changedfiles if: ${{needs.changedfiles.outputs.files}} steps: - name: Checkout Repo uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - name: Link Checker id: lychee uses: lycheeverse/lychee-action@8646ba30535128ac92d33dfc9133794bfdd9b411 # v2.8.0 with: args: "--verbose --no-progress ${{needs.changedfiles.outputs.files}} --config .github/lychee.toml" failIfEmpty: false ================================================ FILE: .github/workflows/check-merge-freeze.yml ================================================ name: Merge freeze on: pull_request: types: [ opened, ready_for_review, synchronize, reopened, labeled, unlabeled, enqueued, ] branches: [main] merge_group: types: [checks_requested] permissions: read-all jobs: check-merge-freeze: name: Check # This condition is to avoid blocking the PR causing the freeze in the first place. if: | (!startsWith(github.event.pull_request.title || github.event.merge_group.head_commit.message, '[chore] Prepare release')) || ((github.event.pull_request.user.login || github.event.merge_group.head_commit.author.name) != 'otelbot[bot]') runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: sparse-checkout: .github/workflows/scripts - run: ./.github/workflows/scripts/check-merge-freeze.sh env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} REPO: open-telemetry/opentelemetry-collector ================================================ FILE: .github/workflows/codeql-analysis.yml ================================================ name: "CodeQL Analysis" on: push: branches: [main] pull_request: concurrency: group: ${{ github.workflow }}-${{ github.ref_name }} cancel-in-progress: true permissions: read-all jobs: CodeQL-Build: permissions: actions: read # for github/codeql-action/init to get workflow details contents: read # for actions/checkout to fetch code security-events: write # for github/codeql-action/autobuild to send a status report runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup Go uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version: oldstable cache: false - name: Cache Go id: go-cache uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: | ~/go/bin ~/go/pkg/mod key: go-cache-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/go.sum') }} # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6 with: languages: go - name: Autobuild uses: github/codeql-action/autobuild@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6 - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6 ================================================ FILE: .github/workflows/contrib-tests.yml ================================================ name: contrib-tests on: push: branches: [main] tags: - v[0-9]+.[0-9]+.[0-9]+.* pull_request: types: [opened, ready_for_review, synchronize, reopened, labeled, unlabeled] branches: [main] merge_group: types: [checks_requested] concurrency: group: ${{ github.workflow }}-${{ github.ref_name }} cancel-in-progress: true permissions: read-all jobs: contrib-tests-prepare: runs-on: ubuntu-latest if: ${{ !contains(github.event.pull_request.labels.*.name, 'Skip Contrib Tests') }} steps: - name: Checkout Repo uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Prepare Contrib Tests run: | contrib_path=/tmp/opentelemetry-collector-contrib git clone --depth=1 https://github.com/open-telemetry/opentelemetry-collector-contrib.git $contrib_path make CONTRIB_PATH=$contrib_path prepare-contrib - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: contrib path: /tmp/opentelemetry-collector-contrib/ include-hidden-files: true contrib-tests-matrix: runs-on: ubuntu-latest needs: [contrib-tests-prepare] if: ${{ !contains(github.event.pull_request.labels.*.name, 'Skip Contrib Tests') }} strategy: fail-fast: false matrix: group: - receiver-0 - receiver-1 - receiver-2 - receiver-3 - processor - exporter-0 - exporter-1 - extension - connector - internal - pkg - cmd-0 - other steps: - name: Checkout Repo uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Download contrib uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 with: name: contrib path: /tmp/contrib - name: Setup Go uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version: oldstable cache: false - name: Cache Go id: go-cache uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: | ~/go/bin ~/go/pkg/mod key: go-cache-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/go.sum') }} - name: Run tests run: | make CONTRIB_PATH=/tmp/contrib SKIP_RESTORE_CONTRIB=true GROUP=${{ matrix.group }} check-contrib contrib_tests: runs-on: ubuntu-latest if: ${{ !contains(github.event.pull_request.labels.*.name, 'Skip Contrib Tests') }} needs: [contrib-tests-matrix] steps: - name: Print result run: echo ${{ needs.contrib-tests-matrix.result }} - name: Interpret result run: | if [[ success == ${{ needs.contrib-tests-matrix.result }} ]] then echo "All matrix jobs passed!" else echo "One or more matrix jobs failed." false fi ================================================ FILE: .github/workflows/fossa.yml ================================================ name: FOSSA scanning on: push: branches: - main permissions: contents: read jobs: fossa: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: fossas/fossa-action@c414b9ad82eaad041e47a7cf62a4f02411f427a0 # v1.8.0 with: api-key: ${{secrets.FOSSA_API_KEY}} team: OpenTelemetry ================================================ FILE: .github/workflows/go-benchmarks.yml ================================================ name: CodSpeed Benchmarks on: push: branches: - "main" pull_request: workflow_dispatch: jobs: benchmarks: name: Run benchmarks runs-on: ubuntu-latest strategy: fail-fast: false matrix: group: - receiver - processor - exporter - pkg steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - run: ./.github/workflows/scripts/free-disk-space.sh - name: Setup Go uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version: stable cache: true - name: Calculate Modules id: calc run: | if [ "${{ matrix.group }}" == "root" ]; then echo "TARGET_MODULES=$(pwd)" >> $GITHUB_ENV else MODULES=$(find ./${{ matrix.group }} -mindepth 1 -maxdepth 2 -type f -name "go.mod" -exec dirname {} \; 2>/dev/null | sort | xargs echo -n || true) if [ -z "$MODULES" ]; then echo "SKIP_BENCH=true" >> $GITHUB_ENV echo "No Go modules found in ${{ matrix.group }}, skipping this job." else echo "TARGET_MODULES=$MODULES" >> $GITHUB_ENV fi fi - name: Run the benchmarks if: env.SKIP_BENCH != 'true' uses: CodSpeedHQ/action@281164b0f014a4e7badd2c02cecad9b595b70537 # v4.11.1 with: mode: walltime run: make for-all-target TARGET="timebenchmark" GOMODULES="${{ env.TARGET_MODULES }}" cache-instruments: true ================================================ FILE: .github/workflows/lint-workflow-files.yml ================================================ name: Lint GitHub Workflow YAML Files on: push: branches: - main pull_request: paths: - '.github/workflows/*.yml' - '.github/workflows/*.yaml' - '.github/actionlint.yaml' permissions: contents: read jobs: lint: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up Go uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6 with: go-version: stable cache: false - name: Cache Go id: go-cache uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: | ~/go/bin ~/go/pkg/mod key: go-cache-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/go.sum') }} - name: Run Actionlint run: | make actionlint - name: Reminder to Address Linting Errors if: failure() run: echo "⚠️ Please address all linting errors before merging this pull request." - name: All linting checks passed if: success() run: echo "✅ All linting checks passed." ================================================ FILE: .github/workflows/milestone-add-to-pr.yml ================================================ # This action adds the "next release" milestone to a pull request # when it is merged name: "Project: Add PR to Milestone" on: pull_request_target: types: - closed permissions: read-all jobs: update-pr: if: github.event.pull_request.merged runs-on: ubuntu-latest permissions: pull-requests: write steps: - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const milestones = await github.rest.issues.listMilestones({ owner: context.repo.owner, repo: context.repo.repo, state: "open" }) for (const milestone of milestones.data) { if (milestone.title == "next release") { await github.rest.issues.update({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, milestone: milestone.number }); return } } ================================================ FILE: .github/workflows/perf.yml ================================================ name: Automation - Performance on: push: branches: [main] permissions: read-all jobs: runperf: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup Go uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version: oldstable cache: false - name: Cache Go id: go-cache uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: | ~/go/bin ~/go/pkg/mod key: go-cache-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/go.sum') }} - name: Run benchmark run: make gobenchmark # Disabling until fine-grained permissions token enabled for the # repository #- name: Store benchmark result # uses: benchmark-action/github-action-benchmark@v1 # with: # tool: 'go' # output-file-path: benchmarks.txt # gh-pages-branch: gh-pages # auto-push: true # github-token: ${{ secrets.GITHUB_TOKEN }} # benchmark-data-dir-path: "docs/dev/bench" ================================================ FILE: .github/workflows/ping-codeowners-issues.yml ================================================ name: 'Ping code owners on issues' on: issues: types: [labeled] permissions: read-all jobs: ping-owners: permissions: issues: write runs-on: ubuntu-24.04 if: ${{ github.repository_owner == 'open-telemetry' }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Run ping-codeowners-issues.sh run: ./.github/workflows/scripts/ping-codeowners-issues.sh env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ISSUE: ${{ github.event.issue.number }} COMPONENT: ${{ github.event.label.name }} ================================================ FILE: .github/workflows/ping-codeowners-on-new-issue.yml ================================================ name: 'Ping code owners on a new issue' on: issues: types: [opened] permissions: read-all jobs: ping-owners-on-new-issue: permissions: issues: write runs-on: ubuntu-24.04 if: ${{ github.repository_owner == 'open-telemetry' }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Run ping-codeowners-on-new-issue.sh run: ./.github/workflows/scripts/ping-codeowners-on-new-issue.sh env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ISSUE: ${{ github.event.issue.number }} TITLE: ${{ github.event.issue.title }} BODY: ${{ github.event.issue.body }} OPENER: ${{ github.event.issue.user.login }} ================================================ FILE: .github/workflows/ping-codeowners-prs.yml ================================================ name: 'Ping code owners on PRs' on: pull_request_target: types: - labeled - ready_for_review permissions: read-all jobs: ping-owners: permissions: pull-requests: write runs-on: ubuntu-24.04 if: ${{ github.actor != 'dependabot[bot]' && github.actor != 'renovate[bot]' && github.repository_owner == 'open-telemetry' && github.event.pull_request.draft == false }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Run ping-codeowners-prs.sh run: ./.github/workflows/scripts/ping-codeowners-prs.sh env: REPO: ${{ github.repository }} AUTHOR: ${{ github.event.pull_request.user.login }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} PR: ${{ github.event.number }} COMPONENT: ${{ github.event.label.name }} ================================================ FILE: .github/workflows/prepare-release.yml ================================================ name: Automation - Prepare Release on: workflow_dispatch: # Determine the version number that will be assigned to the release. During the beta phase, we increment # the minor version number and set the patch number to 0. inputs: candidate-stable: description: Release candidate version (stable, like 1.3.0). Don't include a leading `v`. current-stable: required: true description: Current version (stable, like 1.2.0). Don't include a leading `v`. candidate-beta: description: Release candidate version (beta, like 0.96.0). Don't include `v`. current-beta: required: true description: Current version (beta, like 0.95.1). Don't include `v`. permissions: read-all jobs: validate-versions-format: runs-on: ubuntu-latest steps: - name: Validate version format shell: bash run: | validate_beta_version() { local regex_pattern_beta='^[0-9]+\.[0-9]+\.[0-9]+$' if [[ ! "$1" =~ $regex_pattern_beta ]]; then echo "Invalid $2 version format. For beta, it can be 0.1.0 or higher" exit 1 fi } validate_stable_version() { local regex_pattern_stable='^[1-9][0-9]*\.[0-9]+\.[0-9]+$' if [[ ! "$1" =~ $regex_pattern_stable ]]; then echo "Invalid stable version format for $2. Major version must be greater than 1." exit 1 fi } if [[ ! -z "${{ inputs.candidate-beta }}" ]]; then validate_beta_version "${{ inputs.candidate-beta }}" "candidate-beta" fi validate_beta_version "${{ inputs.current-beta }}" "current-beta" if [[ ! -z "${{ inputs.candidate-stable }}" ]]; then validate_stable_version "${{ inputs.candidate-stable }}" "candidate-stable" fi validate_stable_version "${{ inputs.current-stable }}" "current-stable" if [[ -z "${{ inputs.candidate-beta }}" && -z "${{ inputs.candidate-stable }}" ]]; then echo "Candidate version is not set for beta or stable. Please set a version to proceed." exit 1 fi check-blockers: needs: - validate-versions-format runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 # Make sure that there are no open issues with release:blocker label in Core. The release has to be delayed until they are resolved. - name: Check blockers in core env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} REPO: open-telemetry/opentelemetry-collector run: ./.github/workflows/scripts/release-check-blockers.sh # Make sure that there are no open issues with release:blocker label in Contrib. The release has to be delayed until they are resolved. - name: Check blockers in contrib env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} REPO: open-telemetry/opentelemetry-collector-contrib run: ./.github/workflows/scripts/release-check-blockers.sh # Make sure the current main branch build successfully passes (Core). - name: Check build status in core env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} REPO: open-telemetry/opentelemetry-collector run: ./.github/workflows/scripts/release-check-build-status.sh # Make sure the current main branch build successfully passes (Contrib). - name: Check build status in contrib env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} REPO: open-telemetry/opentelemetry-collector-contrib run: ./.github/workflows/scripts/release-check-build-status.sh create-release-issue: needs: - check-blockers runs-on: ubuntu-latest permissions: issues: write steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 # To keep track of the progress, it might be helpful to create a tracking issue similar to #6067. You are responsible # for all of the steps under the "Performed by collector release manager" heading. Once the issue is created, you can # create the individual ones by hovering them and clicking the "Convert to issue" button on the right hand side. - name: Create issue for tracking release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CANDIDATE_BETA: ${{ inputs.candidate-beta }} CANDIDATE_STABLE: ${{ inputs.candidate-stable }} CURRENT_BETA: ${{ inputs.current-beta }} CURRENT_STABLE: ${{ inputs.current-stable }} REPO: open-telemetry/opentelemetry-collector run: ./.github/workflows/scripts/release-create-tracking-issue.sh # Releasing opentelemetry-collector prepare-release: needs: - check-blockers runs-on: ubuntu-latest permissions: contents: write steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - name: Setup Go uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version: stable cache: false - name: Cache Go id: go-cache uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: | ~/go/bin ~/go/pkg/mod key: go-cache-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/go.sum') }} - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 id: otelbot-token with: app-id: ${{ vars.OTELBOT_APP_ID }} private-key: ${{ secrets.OTELBOT_PRIVATE_KEY }} # Prepare Core for release. # - Update CHANGELOG.md file, this is done via chloggen # - Run make prepare-release PREVIOUS_VERSION=1.0.0 RELEASE_CANDIDATE=1.1.0 MODSET=stable # - Run make prepare-release PREVIOUS_VERSION=0.52.0 RELEASE_CANDIDATE=0.53.0 MODSET=beta - name: Prepare release for core env: GITHUB_TOKEN: ${{ steps.otelbot-token.outputs.token }} REPO: open-telemetry/opentelemetry-collector CANDIDATE_BETA: ${{ inputs.candidate-beta }} CANDIDATE_STABLE: ${{ inputs.candidate-stable }} CURRENT_BETA: ${{ inputs.current-beta }} CURRENT_STABLE: ${{ inputs.current-stable }} run: ./.github/workflows/scripts/release-prepare-release.sh ================================================ FILE: .github/workflows/release-branch.yml ================================================ name: Automation - Release Branch on: push: tags: # Trigger on beta version tags (0.x.x series) to create release branch # This pattern matches: v0.{minor}.{patch} for new releases and bugfix releases - 'v0.[0-9]+.[0-9]+' - 'v0.[0-9]+.[0-9]+-*' # Also support release candidates if needed permissions: contents: read jobs: release-branch: runs-on: ubuntu-latest permissions: contents: write steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - name: Setup Go uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version: oldstable cache: false - name: Cache Go id: go-cache uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: | ~/go/bin ~/go/pkg/mod key: go-cache-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/go.sum') }} - name: Setup Git config run: | git config --global user.name "otelbot" git config --global user.email "197425009+otelbot@users.noreply.github.com" - name: Run release-branch.sh run: | ./.github/workflows/scripts/release-branch.sh env: UPSTREAM_REMOTE_NAME: "origin" MAIN_BRANCH_NAME: "main" GITHUB_REF: ${{ github.ref }} ================================================ FILE: .github/workflows/rerun-workflows.yml ================================================ name: "Rerun Failed Workflows" on: issue_comment: types: - created permissions: read-all jobs: rerun-failed: if: ${{ github.event.issue.pull_request && startsWith(github.event.comment.body, '/rerun') && github.repository_owner == 'open-telemetry' }} permissions: actions: write checks: read runs-on: ubuntu-24.04 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Run rerun-failed-workflows.sh run: ./.github/workflows/scripts/rerun-failed-workflows.sh env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} PR_NUMBER: ${{ github.event.issue.number }} COMMENT: ${{ github.event.comment.body }} SENDER: ${{ github.event.comment.user.login }} ================================================ FILE: .github/workflows/scorecard.yml ================================================ name: Scorecard supply-chain security on: # For Branch-Protection check. Only the default branch is supported. See # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection branch_protection_rule: # To guarantee Maintained check is occasionally updated. See # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained schedule: - cron: '39 1 * * 3' push: branches: [ "main" ] # Declare default permissions as read only. permissions: read-all jobs: analysis: name: Scorecard analysis runs-on: ubuntu-latest permissions: # Needed to upload the results to code-scanning dashboard. security-events: write # Needed to publish results and get a badge (see publish_results below). id-token: write # Uncomment the permissions below if installing in a private repository. # contents: read # actions: read steps: - name: "Checkout code" uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: "Run analysis" uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 with: results_file: results.sarif results_format: sarif # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: # - you want to enable the Branch-Protection check on a *public* repository, or # - you are installing Scorecard on a *private* repository # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. # repo_token: ${{ secrets.SCORECARD_TOKEN }} # Public repositories: # - Publish results to OpenSSF REST API for easy access by consumers # - Allows the repository to include the Scorecard badge. # - See https://github.com/ossf/scorecard-action#publishing-results. # For private repositories: # - `publish_results` will always be set to `false`, regardless # of the value entered here. publish_results: true # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: SARIF file path: results.sarif retention-days: 5 # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6 with: sarif_file: results.sarif ================================================ FILE: .github/workflows/scripts/add-labels-and-owners.sh ================================================ #!/usr/bin/env bash # # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 # # Adds code owners without write access as reviewers on a PR. Note that # the code owners must still be a member of the `open-telemetry` # organization. # # Note that since this script is considered a requirement for PRs, # it should never fail. set -euo pipefail if [[ -z "${REPO:-}" || -z "${PR:-}" ]]; then echo "One or more of REPO and PR have not been set, please ensure each is set." exit 0 fi main () { CUR_DIRECTORY=$(dirname "$0") # Reviews may have comments that need to be cleaned up for jq, # so restrict output to only printable characters and ensure escape # sequences are removed. # The latestReviews key only returns the latest review for each reviewer, # cutting out any other reviews. We use that instead of requestedReviews # since we need to get the list of users eligible for requesting another # review. The GitHub CLI does not offer a list of all reviewers, which # is only available through the API. To cut down on API calls to GitHub, # we use the latest reviews to determine which users to filter out. JSON=$(gh pr view "${PR}" --json "files,author,latestReviews" | LC_ALL=C tr -dc '[:print:]' | sed -E 's/\\[a-z]//g') AUTHOR=$(echo -n "${JSON}"| jq -r '.author.login') FILES=$(echo -n "${JSON}"| jq -r '.files[].path') REVIEW_LOGINS=$(echo -n "${JSON}"| jq -r '.latestReviews[].author.login') COMPONENTS=$(bash "${CUR_DIRECTORY}/get-components.sh") REVIEWERS="" LABELS="" declare -A PROCESSED_COMPONENTS declare -A REVIEWED for REVIEWER in ${REVIEW_LOGINS}; do # GitHub adds "app/" in front of user logins. The API docs don't make # it clear what this means or whether it will always be present. The # '/' character isn't a valid character for usernames, so this won't # replace characters within a username. REVIEWED["@${REVIEWER//app\//}"]=true done if [[ -v REVIEWED[@] ]]; then echo "Users that have already reviewed this PR and will not have another review requested:" "${!REVIEWED[@]}" else echo "This PR has not yet been reviewed, all code owners are eligible for a review request" fi RISKY_REGEX=' ^.github/workflows/prepare-release.yml$ |^.github/workflows/scripts/release-prepare-release.sh$ |^Makefile$ |^Makefile.Common$ ' RISKY_REGEX="$(echo "$RISKY_REGEX" | tr -d ' \n')" RISKY_FILES="$(echo "$FILES" | grep -E "$RISKY_REGEX")" if [[ -n "${RISKY_FILES}" ]]; then echo "This PR may affect the release process, as it touches the following files:" \ "$(echo "$RISKY_FILES" | sed -E 's/\n/, /')" LABELS="release:risky-change" else echo "This PR does not have release-affecting changes." fi for COMPONENT in ${COMPONENTS}; do # Files will be in alphabetical order and there are many files to # a component, so loop through files in an inner loop. This allows # us to remove all files for a component from the list so they # won't be checked against the remaining components in the components # list. This provides a meaningful speedup in practice. for FILE in ${FILES}; do MATCH=$(echo -n "${FILE}" | grep -E "^${COMPONENT}" || true) if [[ -z "${MATCH}" ]]; then continue fi # If we match a file with a component we don't need to process the file again. FILES=$(echo -n "${FILES}" | grep -v "${FILE}") if [[ -v PROCESSED_COMPONENTS["${COMPONENT}"] ]]; then continue fi PROCESSED_COMPONENTS["${COMPONENT}"]=true OWNERS=$(COMPONENT="${COMPONENT}" bash "${CUR_DIRECTORY}/get-codeowners.sh") for OWNER in ${OWNERS}; do # Users that leave reviews are removed from the "requested reviewers" # list and are eligible to have another review requested. We only want # to request a review once, so remove them from the list. if [[ -v REVIEWED["${OWNER}"] || "${OWNER}" = "@${AUTHOR}" ]]; then continue fi if [[ -n "${REVIEWERS}" ]]; then REVIEWERS+="," fi REVIEWERS+=$(echo -n "${OWNER}" | sed -E 's/@(.+)/"\1"/') done # Convert the CODEOWNERS entry to a label COMPONENT_NAME=$(echo -n "${COMPONENT}" | sed -E 's%^(.+)/(.+)\1%\1/\2%') if (( "${#COMPONENT_NAME}" > 50 )); then echo "'${COMPONENT_NAME}' exceeds GitHub's 50-character limit on labels, skipping adding label" continue fi if [[ -n "${LABELS}" ]]; then LABELS+="," fi LABELS+="${COMPONENT_NAME}" done done if [[ -n "${LABELS}" ]]; then echo "Adding labels: ${LABELS}" gh pr edit "${PR}" --add-label "${LABELS}" || echo "Failed to add labels" else echo "No labels found" fi # Note that adding the labels above will not trigger any other workflows to # add code owners, so we have to do it here. # # We have to use the GitHub API directly due to an issue with how the CLI # handles PR updates that causes it require access to organization teams, # and the GitHub token doesn't provide that permission. # For more: https://github.com/cli/cli/issues/4844 # # The GitHub API validates that authors are not requested to review, but # accepts duplicate logins and logins that are already reviewers. if [[ -n "${REVIEWERS}" ]]; then echo "Requesting review from ${REVIEWERS}" curl \ -X POST \ -H "Accept: application/vnd.github+json" \ -H "Authorization: Bearer ${GITHUB_TOKEN}" \ "https://api.github.com/repos/${REPO}/pulls/${PR}/requested_reviewers" \ -d "{\"reviewers\":[${REVIEWERS}]}" \ | jq ".message" \ || echo "jq was unable to parse GitHub's response" else echo "No code owners found" fi } # We don't want this workflow to ever fail and block a PR, # so ensure all errors are caught. main || echo "Failed to run $0" ================================================ FILE: .github/workflows/scripts/add-labels-command.sh ================================================ #!/usr/bin/env bash # # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 # # set -euo pipefail if [[ -z "${ISSUE:-}" || -z "${COMMENT:-}" || -z "${SENDER:-}" ]]; then echo "At least one of ISSUE, COMMENT, or SENDER has not been set, please ensure each is set." exit 0 fi CUR_DIRECTORY=$(dirname "$0") if [[ ${COMMENT:0:6} != "/label" ]]; then echo "Comment is not a label comment, exiting." exit 0 fi # key: label in comment # value: actual label declare -A COMMON_LABELS COMMON_LABELS["arm64"]="arm64" COMMON_LABELS["good-first-issue"]="good first issue" COMMON_LABELS["help-wanted"]="help wanted" COMMON_LABELS["discussion-needed"]="discussion-needed" COMMON_LABELS["os:macos"]="os:macos" COMMON_LABELS["os:windows"]="os:windows" COMMON_LABELS["waiting-for-author"]="waiting-for-author" COMMON_LABELS["waiting-for-codeowners"]="waiting-for-codeowners" COMMON_LABELS["bug"]="bug" COMMON_LABELS["priority:p0"]="priority:p0" COMMON_LABELS["priority:p1"]="priority:p1" COMMON_LABELS["priority:p2"]="priority:p2" COMMON_LABELS["priority:p3"]="priority:p3" COMMON_LABELS["stale"]="Stale" LABELS=$(echo "${COMMENT}" | sed -E 's%^/label%%') for LABEL_REQ in ${LABELS}; do LABEL=$(echo "${LABEL_REQ}" | sed -E s/^[+-]?//) # Trim newlines from label that would cause matching to fail LABEL=$(echo "${LABEL}" | tr -d '\n') SHOULD_ADD=true if [[ "${LABEL_REQ:0:1}" = "-" ]]; then SHOULD_ADD=false fi if [[ -v COMMON_LABELS["${LABEL}"] ]]; then if [[ ${SHOULD_ADD} = true ]]; then gh issue edit "${ISSUE}" --add-label "${COMMON_LABELS["${LABEL}"]}" else gh issue edit "${ISSUE}" --remove-label "${COMMON_LABELS["${LABEL}"]}" fi continue fi # Grep exits with status code 1 if there are no matches, # so we manually set RESULT to 0 if nothing is found. RESULT=$(grep -c "${LABEL}" .github/CODEOWNERS || true) if [[ ${RESULT} = 0 ]]; then echo "\"${LABEL}\" doesn't correspond to a component, skipping." continue fi if [[ ${SHOULD_ADD} = true ]]; then gh issue edit "${ISSUE}" --add-label "${LABEL}" # Labels added by a GitHub Actions workflow don't trigger other workflows # by design, so we have to manually ping code owners here. COMPONENT="${LABEL}" ISSUE=${ISSUE} SENDER="${SENDER}" bash "${CUR_DIRECTORY}/ping-codeowners-issues.sh" else gh issue edit "${ISSUE}" --remove-label "${LABEL}" fi done ================================================ FILE: .github/workflows/scripts/check-merge-freeze.sh ================================================ #!/bin/bash -e # # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 # Check for [chore] Prepare release PRs in core repo BLOCKERS=$( gh pr list -A "otelbot[bot]" -S "[chore] Prepare release" --json url -q '.[].url' -R "${REPO}" ) # Check for [chore] Update core dependencies PRs in opentelemetry-collector-contrib CONTRIB_REPO="open-telemetry/opentelemetry-collector-contrib" CONTRIB_BLOCKERS=$( gh pr list -A "otelbot[bot]" -S "[chore] Update core dependencies" --json url -q '.[].url' -R "${CONTRIB_REPO}" ) # Combine both blockers BLOCKERS="${BLOCKERS}${BLOCKERS:+ }${CONTRIB_BLOCKERS}" if [ "${BLOCKERS}" != "" ]; then echo "Merging in main is frozen, as there are open release/update PRs: ${BLOCKERS}" echo "If you believe this is no longer true, re-run this job to unblock your PR." exit 1 fi ================================================ FILE: .github/workflows/scripts/free-disk-space.sh ================================================ #!/usr/bin/env bash # # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 echo "Available disk space before:" df -h / # The Android SDK is the biggest culprit for the lack of disk space in CI. # It is installed into /usr/local/lib/android manually (ie. not with apt) by this script: # https://github.com/actions/runner-images/blob/main/images/ubuntu/scripts/build/install-android-sdk.sh # so let's delete the directory manually. echo "Deleting unused Android SDK and tools..." sudo rm -rf /usr/local/lib/android echo "Available disk space after:" df -h / ================================================ FILE: .github/workflows/scripts/get-codeowners.sh ================================================ #!/usr/bin/env bash # # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 # # This script checks the GitHub CODEOWNERS file for any code owners # of contrib components and returns a string of the code owners if it # finds them. set -euo pipefail get_component_type() { echo "${COMPONENT}" | cut -f 1 -d '/' } get_codeowners() { # grep arguments explained: # -m 1: Match the first occurrence # ^: Match from the beginning of the line # ${1}: Insert first argument given to this function # [\/]\?: Match 0 or 1 instances of a forward slash # \s: Match any whitespace character (grep -m 1 "^${1}[\/]\?\s" .github/CODEOWNERS || true) | \ sed 's/ */ /g' | \ cut -f3- -d ' ' } if [[ -z "${COMPONENT:-}" ]]; then echo "COMPONENT has not been set, please ensure it is set." exit 1 fi OWNERS="$(get_codeowners "${COMPONENT}")" if [[ -z "${OWNERS:-}" ]]; then COMPONENT_TYPE=$(get_component_type "${COMPONENT}") OWNERS="$(get_codeowners "${COMPONENT}${COMPONENT_TYPE}")" fi echo "${OWNERS}" ================================================ FILE: .github/workflows/scripts/get-components.sh ================================================ #!/usr/bin/env sh # # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 # # Get a list of components within the repository that have some form of ownership # ascribed to them. grep -E '^[A-Za-z0-9/]' .github/CODEOWNERS | \ awk '{ print $1 }' | \ sed -E 's%(.+)/$%\1%' ================================================ FILE: .github/workflows/scripts/ping-codeowners-issues.sh ================================================ #!/usr/bin/env bash # # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 # # set -euo pipefail if [[ -z "${COMPONENT:-}" || -z "${ISSUE:-}" ]]; then echo "Either COMPONENT or ISSUE has not been set, please ensure both are set." exit 0 fi CUR_DIRECTORY=$(dirname "$0") OWNERS=$(COMPONENT="${COMPONENT}" bash "${CUR_DIRECTORY}/get-codeowners.sh") if [[ -z "${OWNERS}" ]]; then exit 0 fi gh issue comment "${ISSUE}" --body "Pinging code owners for ${COMPONENT}: ${OWNERS}. See [Adding Labels via Comments](https://github.com/open-telemetry/opentelemetry-collector/blob/main/CONTRIBUTING.md#adding-labels-via-comments) if you do not have permissions to add labels yourself. For example, comment '/label priority:p2 -needs-triage' to set the priority and remove the needs-triage label." ================================================ FILE: .github/workflows/scripts/ping-codeowners-on-new-issue.sh ================================================ #!/usr/bin/env bash # # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 # # set -euo pipefail if [[ -z "${ISSUE:-}" || -z "${TITLE:-}" || -z "${BODY:-}" || -z "${OPENER:-}" ]]; then echo "Missing one of ISSUE, TITLE, BODY, or OPENER, please ensure all are set." exit 0 fi LABELS_COMMENT='See [Adding Labels via Comments](https://github.com/open-telemetry/opentelemetry-collector/blob/main/CONTRIBUTING.md#adding-labels-via-comments) if you do not have permissions to add labels yourself.' CUR_DIRECTORY=$(dirname "$0") LABELS="" PING_LINES="" declare -A PINGED_COMPONENTS TITLE_COMPONENT=$(echo "${TITLE}" | (grep -oE "\[.+\]" || true) | sed -E 's/\[(.+)\]/\1/' | sed -E 's%^(.+)/(.+)\1%\1/\2%') COMPONENTS_SECTION_START=$( (echo "${BODY}" | grep -n '### Component(s)' | awk '{ print $1 }' | grep -oE '[0-9]+') || echo '-1' ) BODY_COMPONENTS="" if [[ "${COMPONENTS_SECTION_START}" != '-1' ]]; then BODY_COMPONENTS=$(echo "${BODY}" | sed -n $((COMPONENTS_SECTION_START+2))p) fi if [[ -n "${TITLE_COMPONENT}" && ! ("${TITLE_COMPONENT}" =~ " ") ]]; then CODEOWNERS=$(COMPONENT="${TITLE_COMPONENT}" "${CUR_DIRECTORY}/get-codeowners.sh" || true) if [[ -n "${CODEOWNERS}" ]]; then PING_LINES+="- ${TITLE_COMPONENT}: ${CODEOWNERS}\n" PINGED_COMPONENTS["${TITLE_COMPONENT}"]=1 if (( "${#TITLE_COMPONENT}" <= 50 )); then LABELS+="${TITLE_COMPONENT}" else echo "'${TITLE_COMPONENT}' exceeds GitHub's 50-character limit, skipping adding a label" fi fi fi for COMPONENT in ${BODY_COMPONENTS}; do # Comments are delimited by ', ' and the for loop separates on spaces, so remove the extra comma. COMPONENT=${COMPONENT//,/} CODEOWNERS=$(COMPONENT="${COMPONENT}" "${CUR_DIRECTORY}/get-codeowners.sh" || true) if [[ -n "${CODEOWNERS}" ]]; then if [[ -v PINGED_COMPONENTS["${COMPONENT}"] ]]; then continue fi PING_LINES+="- ${COMPONENT}: ${CODEOWNERS}\n" PINGED_COMPONENTS["${COMPONENT}"]=1 if (( "${#COMPONENT}" > 50 )); then echo "'${COMPONENT}' exceeds GitHub's 50-character limit on labels, skipping adding a label" continue fi if [[ -n "${LABELS}" ]]; then LABELS+="," fi LABELS+="${COMPONENT}" fi done if [[ -v PINGED_COMPONENTS[@] ]]; then echo "The issue was associated with components:" "${!PINGED_COMPONENTS[@]}" else echo "No related components were given" fi if [[ -n "${LABELS}" ]]; then # Notes on this call: # 1. Labels will be deduplicated by the GitHub CLI. # 2. The call to edit the issue will fail if any of the # labels doesn't exist. We can be reasonably sure that # all labels will exist since they come from a known set. echo "Adding the following labels: ${LABELS//,/ /}" gh issue edit "${ISSUE}" --add-label "${LABELS}" || true else echo "No labels were found to add" fi if [[ -n "${PING_LINES}" ]]; then # Notes on this call: # 1. Adding labels above will not trigger the ping-codeowners flow, # since GitHub Actions disallows triggering a workflow from a # workflow, so we have to ping code owners here. # 2. The GitHub CLI only offers multiline strings through file input, # so we provide the comment through stdin. # 3. The PING_LINES variable must be directly put into the echo string # to get the newlines to render correctly, using string formatting # causes the newlines to be interpreted literally. echo -e "Pinging code owners:\n${PING_LINES}" echo -e "Pinging code owners:\n${PING_LINES}\n" "${LABELS_COMMENT}" \ | gh issue comment "${ISSUE}" -F - else echo "No code owners were found to ping" fi ================================================ FILE: .github/workflows/scripts/ping-codeowners-prs.sh ================================================ #!/usr/bin/env bash # # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 # set -euo pipefail if [[ -z "${REPO:-}" || -z "${AUTHOR:-}" || -z "${COMPONENT:-}" || -z "${PR:-}" ]]; then echo "At least one of REPO, AUTHOR, COMPONENT, or PR has not been set, please ensure each is set." exit 0 fi CUR_DIRECTORY=$(dirname "$0") main() { OWNERS=$(COMPONENT="${COMPONENT}" bash "${CUR_DIRECTORY}/get-codeowners.sh") REVIEWERS="" if [[ -z "${OWNERS}" ]]; then exit 0 fi for OWNER in ${OWNERS}; do if [[ "${OWNER}" = "@${AUTHOR}" ]]; then continue fi if [[ -n "${REVIEWERS}" ]]; then REVIEWERS+="," fi REVIEWERS+=$(echo "${OWNER}" | sed -E 's/@(.+)/"\1"/') done # We have to use the GitHub API directly due to an issue with how the CLI # handles PR updates that causes it require access to organization teams, # and the GitHub token doesn't provide that permission. # For more: https://github.com/cli/cli/issues/4844 # # The GitHub API validates that authors are not requested to review, but # accepts duplicate logins and logins that are already reviewers. echo "Requesting review from code owners: ${REVIEWERS}" curl \ -X POST \ -H "Accept: application/vnd.github+json" \ -H "Authorization: Bearer ${GITHUB_TOKEN}" \ "https://api.github.com/repos/${REPO}/pulls/${PR}/requested_reviewers" \ -d "{\"reviewers\":[${REVIEWERS}]}" \ | jq ".message" \ || echo "jq was unable to parse GitHub's response" } # We don't want this workflow to ever fail and block a PR, # so ensure all errors are caught. main || echo "Failed to run $0" ================================================ FILE: .github/workflows/scripts/release-branch.sh ================================================ #!/bin/bash -ex # # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 # --- Configuration --- UPSTREAM_REMOTE_NAME=${UPSTREAM_REMOTE_NAME:-"upstream"} # Your upstream remote name for open-telemetry/opentelemetry-collector MAIN_BRANCH_NAME=${MAIN_BRANCH_NAME:-"main"} LOCAL_MAIN_BRANCH_NAME=${LOCAL_MAIN_BRANCH_NAME:-"${MAIN_BRANCH_NAME}"} # These variables are only used if git user.name and git user.email are not configured GIT_CONFIG_USER_NAME=${GIT_CONFIG_USER_NAME:-"otelbot"} GIT_CONFIG_USER_EMAIL=${GIT_CONFIG_USER_EMAIL:-"197425009+otelbot@users.noreply.github.com"} # --- Extract release information from tag --- if [[ -z "$GITHUB_REF" ]]; then echo "Error: GITHUB_REF environment variable must be provided when running in GitHub Actions." echo "For manual usage: GITHUB_REF=refs/tags/v0.85.0 $0" exit 1 fi # Extract tag name and validate format using regex if [[ ! $GITHUB_REF =~ ^refs/tags/v([0-9]+\.[0-9]+)\.[0-9]+(-.+)?$ ]]; then echo "Error: GITHUB_REF did not match expected format (refs/tags/vX.XX.X)" exit 1 fi # Extract version numbers from regex match VERSION_MAJOR_MINOR=${BASH_REMATCH[1]} RELEASE_SERIES="v${VERSION_MAJOR_MINOR}.x" echo "Release series: ${RELEASE_SERIES}" # --- Use current commit as prepare release commit --- PREPARE_RELEASE_COMMIT_HASH="${GITHUB_SHA:-HEAD}" echo "Using current commit as prepare release commit: ${PREPARE_RELEASE_COMMIT_HASH}" RELEASE_BRANCH_NAME="release/${RELEASE_SERIES}" echo "Automating Release Steps for: ${RELEASE_SERIES}" echo "Release Branch Name: ${RELEASE_BRANCH_NAME}" echo "'Prepare release' commit hash: ${PREPARE_RELEASE_COMMIT_HASH}" echo "Upstream Remote: ${UPSTREAM_REMOTE_NAME}" echo "--------------------------------------------------" # --- Step 4: Checkout main, Pull, Create/Update and Push Release Branch --- echo "" echo "=== Step 4: Preparing and Pushing Release Branch ===" # 1. Checkout main git checkout "${LOCAL_MAIN_BRANCH_NAME}" # 2. Fetch from upstream (updates remote-tracking branches including potential existing release branch) git fetch "${UPSTREAM_REMOTE_NAME}" # 3. Rebase local main with upstream/main git rebase "${UPSTREAM_REMOTE_NAME}/${MAIN_BRANCH_NAME}" echo "'${LOCAL_MAIN_BRANCH_NAME}' branch is now up-to-date." # Verify the commit exists (it should be reachable from main after fetch) if ! git cat-file -e "${PREPARE_RELEASE_COMMIT_HASH}^{commit}" 2>/dev/null; then echo "Error: Provided 'Prepare release' commit hash '${PREPARE_RELEASE_COMMIT_HASH}' not found." exit 1 fi # 4. Handle Release Branch: Check existence, create or switch, and merge/base BRANCH_EXISTS_LOCALLY=$(git branch --list "${RELEASE_BRANCH_NAME}") # Check remote by looking for the remote tracking branch ref after fetch if git rev-parse --verify --quiet "${UPSTREAM_REMOTE_NAME}/${RELEASE_BRANCH_NAME}" > /dev/null 2>&1; then BRANCH_EXISTS_REMOTELY=true fi if [[ -n "$BRANCH_EXISTS_LOCALLY" ]]; then echo "Release branch '${RELEASE_BRANCH_NAME}' found locally." echo "Please delete local release branch using 'git branch -D ${RELEASE_BRANCH_NAME}' and run the script again." exit 1 elif [[ -n "$BRANCH_EXISTS_REMOTELY" ]]; then echo "Release branch '${RELEASE_BRANCH_NAME}' found on remote '${UPSTREAM_REMOTE_NAME}'." echo "Nothing to do, exiting." exit 0 else echo "Release branch '${RELEASE_BRANCH_NAME}' not found locally or on remote." git switch -c "${RELEASE_BRANCH_NAME}" "${PREPARE_RELEASE_COMMIT_HASH}" fi echo "Current branch is now '${RELEASE_BRANCH_NAME}'." git --no-pager log -1 --pretty=oneline # Show the commit at the tip of the release branch # 5. Push the release branch to upstream git push -u "${UPSTREAM_REMOTE_NAME}" "${RELEASE_BRANCH_NAME}" echo "Branch '${RELEASE_BRANCH_NAME}' pushed (or updated) on '${UPSTREAM_REMOTE_NAME}'." echo "Step 4 completed." echo "--------------------------------------------------" echo "" echo "Automation for release branch creation complete." echo "Release branch '${RELEASE_BRANCH_NAME}' has been created from the prepare release commit." echo "Tag-triggered build workflows should now be running for the pushed tags." ================================================ FILE: .github/workflows/scripts/release-check-blockers.sh ================================================ #!/bin/bash -ex # # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 BLOCKERS=$( gh issue list --search "label:release:blocker" --json url --jq '.[].url' --repo "${REPO}" ) if [ "${BLOCKERS}" != "" ]; then echo "Release blockers in ${REPO} repo: ${BLOCKERS}" exit 1 fi ================================================ FILE: .github/workflows/scripts/release-check-build-status.sh ================================================ #!/bin/bash -ex # # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 BRANCH=main WORKFLOW=build-and-test RESULT=$(gh run list --branch "${BRANCH}" --json status --jq '[.[] | select(.status != "queued" and .status != "in_progress")][0].status' --workflow "${WORKFLOW}" --repo "${REPO}" ) if [ "${RESULT}" != "completed" ]; then echo "Build status in ${REPO} is not completed: ${RESULT}" gh run list --branch "${BRANCH}" --json status,url --jq '[.[] | select(.status != "queued" and .status != "in_progress")][0].url' --workflow "${WORKFLOW}" --repo "${REPO}" exit 1 fi ================================================ FILE: .github/workflows/scripts/release-create-tracking-issue.sh ================================================ #!/bin/bash -ex # # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 RELEASE_VERSION=v${CANDIDATE_STABLE}/v${CANDIDATE_BETA} if [ "${CANDIDATE_STABLE}" == "" ]; then RELEASE_VERSION="v${CANDIDATE_BETA}" fi if [ "${CANDIDATE_BETA}" == "" ]; then RELEASE_VERSION="v${CANDIDATE_STABLE}" fi EXISTING_ISSUE=$( gh issue list --search "in:title Release ${RELEASE_VERSION}" --json url --jq '.[].url' --repo "${REPO}" --state open --label release ) if [ "${EXISTING_ISSUE}" != "" ]; then echo "Issue already exists: ${EXISTING_ISSUE}" exit 0 fi gh issue create -a "${GITHUB_ACTOR}" --repo "${REPO}" --label release --title "Release ${RELEASE_VERSION}" --body "Like #14236, but for ${RELEASE_VERSION} **Performed by collector release manager** - [ ] Prepare core release ${RELEASE_VERSION} - [ ] Tag and release core ${RELEASE_VERSION} **Performed by collector contrib release manager** - [ ] Prepare contrib release v${CANDIDATE_BETA} - [ ] Tag and release contrib v${CANDIDATE_BETA} **Performed by collector releases release manager** - [ ] Prepare otelcol containers images release v${CANDIDATE_BETA} - [ ] Tag and otelcol containers images release v${CANDIDATE_BETA} **Performed by operator maintainers** - [ ] Release the operator v${CANDIDATE_BETA} **Performed by helm chart maintainers** - [ ] Update the opentelemetry-collector helm chart to use v${CANDIDATE_BETA}" ================================================ FILE: .github/workflows/scripts/release-prepare-release.sh ================================================ #!/bin/bash -ex # # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 if [ "${CANDIDATE_STABLE}" == "" ] && [ "${CANDIDATE_BETA}" == "" ]; then echo "One of CANDIDATE_STABLE or CANDIDATE_BETA must be set" exit 1 fi # Expand CURRENT_STABLE and CURRENT_BETA to escape . character by using [.] CURRENT_STABLE_ESCAPED=${CURRENT_STABLE//./[.]} CURRENT_BETA_ESCAPED=${CURRENT_BETA//./[.]} RELEASE_VERSION=v${CANDIDATE_STABLE}/v${CANDIDATE_BETA} if [ "${CANDIDATE_STABLE}" == "" ]; then RELEASE_VERSION="v${CANDIDATE_BETA}" fi if [ "${CANDIDATE_BETA}" == "" ]; then RELEASE_VERSION="v${CANDIDATE_STABLE}" fi make chlog-update VERSION="${RELEASE_VERSION}" COMMANDS="- make chlog-update VERSION=${RELEASE_VERSION}" git config user.name otelbot git config user.email 197425009+otelbot@users.noreply.github.com BRANCH="prepare-release-prs/${CANDIDATE_BETA}" git checkout -b "${BRANCH}" git add --all git commit -m "Changelog update ${RELEASE_VERSION}" if [ "${CANDIDATE_STABLE}" != "" ]; then make prepare-release PREVIOUS_VERSION="${CURRENT_STABLE_ESCAPED}" RELEASE_CANDIDATE="${CANDIDATE_STABLE}" MODSET=stable COMMANDS+=" - make prepare-release PREVIOUS_VERSION=${CURRENT_STABLE_ESCAPED} RELEASE_CANDIDATE=${CANDIDATE_STABLE} MODSET=stable" fi if [ "${CANDIDATE_BETA}" != "" ]; then make prepare-release PREVIOUS_VERSION="${CURRENT_BETA_ESCAPED}" RELEASE_CANDIDATE="${CANDIDATE_BETA}" MODSET=beta COMMANDS+=" - make prepare-release PREVIOUS_VERSION=${CURRENT_BETA_ESCAPED} RELEASE_CANDIDATE=${CANDIDATE_BETA} MODSET=beta" fi git push --set-upstream origin "${BRANCH}" # Use OpenTelemetryBot account to create PR, allowing workflows to run # The title must match the checks in check-merge-freeze.yml gh pr create --head "$(git branch --show-current)" --title "[chore] Prepare release ${RELEASE_VERSION}" --body " The following commands were run to prepare this release: ${COMMANDS} " ================================================ FILE: .github/workflows/scripts/rerun-failed-workflows.sh ================================================ #!/usr/bin/env bash # # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 # set -euo pipefail if [[ -z "${PR_NUMBER:-}" || -z "${COMMENT:-}" || -z "${SENDER:-}" ]]; then echo "PR_NUMBER, COMMENT, or SENDER not set" exit 0 fi if [[ ${COMMENT:0:6} != "/rerun" ]]; then echo "Not a rerun command" exit 0 fi PR_DATA=$(gh pr view "${PR_NUMBER}" --json headRefOid,author) HEAD_SHA=$(echo "${PR_DATA}" | jq -r '.headRefOid') PR_AUTHOR=$(echo "${PR_DATA}" | jq -r '.author.login') if [[ "${SENDER}" != "${PR_AUTHOR}" ]]; then echo "Only PR author can rerun workflows" exit 0 fi echo "Finding failed workflows for commit: ${HEAD_SHA}" FAILED_RUNS=$(gh run list \ --commit "${HEAD_SHA}" \ --status failure \ --json databaseId \ --jq '.[].databaseId') if [[ -z "${FAILED_RUNS}" ]]; then echo "No failed workflows found" exit 0 else for RUN_ID in ${FAILED_RUNS}; do echo "Rerunning workflow: ${RUN_ID}" gh run rerun "${RUN_ID}" --failed done fi ================================================ FILE: .github/workflows/scripts/win-required-ports.ps1 ================================================ <# .SYNOPSIS This script ensures that the ports required by the default configuration of the collector are available. .DESCRIPTION Certain runs on GitHub Actions sometimes have ports required by the default configuration reserved by other applications via the WinNAT service. #> #Requires -RunAsAdministrator netsh interface ip show excludedportrange protocol=tcp Stop-Service winnat # Only port in the dynamic range that is being, from time to time, reserved by other applications. netsh interface ip add excludedportrange protocol=tcp startport=55679 numberofports=1 Start-Service winnat netsh interface ip show excludedportrange protocol=tcp ================================================ FILE: .github/workflows/shellcheck.yml ================================================ name: Shellcheck lint on: push: branches: [main] pull_request: branches: [main] merge_group: types: [checks_requested] permissions: read-all jobs: shellcheck: name: Shellcheck runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Run ShellCheck uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # 2.0.0 ================================================ FILE: .github/workflows/sourcecode-release.yaml ================================================ name: Source Code - Release on: push: tags: - "v*" permissions: contents: read jobs: goreleaser: runs-on: ubuntu-latest permissions: contents: write # Grant write permissions to repository contents issues: write # Grant write permissions to PR milestones steps: - name: Checkout Repo uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 # Extract title from latest version title in CHANGELOG.md - name: Prepare release title id: release-title run: | echo "title=$(grep -A 2 '' CHANGELOG.md | awk '/##/{print $2}')" >> $GITHUB_OUTPUT - name: Prepare release notes run: | touch release-notes.md echo "### Images and binaries here: https://github.com/open-telemetry/opentelemetry-collector-releases/releases/tag/${{ github.ref_name }}" >> release-notes.md echo "" >> release-notes.md echo "## End User Changelog" >> release-notes.md awk '//,//' CHANGELOG.md > tmp-chlog.md # select changelog of latest version only sed '1,3d' tmp-chlog.md >> release-notes.md # delete first 3 lines of file echo "" >> release-notes.md echo "## API Changelog" >> release-notes.md awk '//,//' CHANGELOG-API.md > tmp-chlog-api.md # select changelog of latest version only sed '1,3d' tmp-chlog-api.md >> release-notes.md # delete first 3 lines of file - name: Create Github Release run: | gh release create ${{ github.ref_name }} -t ${{ steps.release-title.outputs.title }} -F release-notes.md env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const milestones = await github.rest.issues.listMilestones({ owner: context.repo.owner, repo: context.repo.repo, state: "open" }) for (const milestone of milestones.data) { if (milestone.title == "next release") { await github.rest.issues.updateMilestone({ owner: context.repo.owner, repo: context.repo.repo, milestone_number: milestone.number, title: "${{ github.ref_name }}" }); await github.rest.issues.createMilestone({ owner: context.repo.owner, repo: context.repo.repo, title: "next release" }); return } } ================================================ FILE: .github/workflows/spell-check.yaml ================================================ name: Spell Check on: push: branches: [main] pull_request: permissions: contents: read jobs: spell-check: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Run cSpell uses: streetsidesoftware/cspell-action@9cd41bb518a24fefdafd9880cbab8f0ceba04d28 # v8.3.0 with: incremental_files_only: false use_cspell_files: true config: '.github/workflows/utils/cspell.json' ================================================ FILE: .github/workflows/stale-pr.yaml ================================================ name: "Close stale pull requests" on: schedule: - cron: "12 3 * * *" # arbitrary time not to DDOS GitHub permissions: read-all jobs: stale: permissions: issues: write # for actions/stale to close stale issues pull-requests: write # for actions/stale to close stale PRs runs-on: ubuntu-latest steps: - uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-pr-message: "This PR was marked stale due to lack of activity. It will be closed in 14 days." close-pr-message: "Closed as inactive. Feel free to reopen if this PR is still being worked on." days-before-pr-stale: 14 days-before-issue-stale: 730 days-before-pr-close: 14 days-before-issue-close: 30 ================================================ FILE: .github/workflows/survey-on-merged-pr.yml ================================================ name: Survey on Merged PR by Non-Member on: pull_request_target: types: [closed] permissions: contents: read env: PR_NUM: ${{ github.event.pull_request.number }} SURVEY_URL: https://docs.google.com/forms/d/e/1FAIpQLSf2FfCsW-DimeWzdQgfl0KDzT2UEAqu69_f7F2BVPSxVae1cQ/viewform?entry.1540511742=open-telemetry/opentelemetry-collector jobs: comment-on-pr: name: Add survey to PR if author is not a member runs-on: ubuntu-latest if: github.event.pull_request.merged == true steps: - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 id: otelbot-token with: app-id: ${{ vars.OTELBOT_APP_ID }} private-key: ${{ secrets.OTELBOT_PRIVATE_KEY }} - name: Add survey comment if author is not a member or bot run: | USERNAME="${{ github.event.pull_request.user.login }}" USER_TYPE="${{ github.event.pull_request.user.type }}" ORG="${{ github.repository_owner }}" # Skip if user is a bot if [[ "$USER_TYPE" == "Bot" ]]; then echo "Skipping survey for bot user: $USERNAME" exit 0 fi # Skip if user is an org member if gh api "orgs/$ORG/members/$USERNAME" --silent; then echo "Skipping survey for org member: $USERNAME" exit 0 fi # Add survey comment for external contributor echo "Adding survey comment for external contributor: $USERNAME" gh pr comment ${PR_NUM} --repo ${{ github.repository }} --body "Thank you for your contribution @${USERNAME}! 🎉 We would like to hear from you about your experience contributing to OpenTelemetry by taking a few minutes to fill out this [survey](${SURVEY_URL})." env: GH_TOKEN: ${{ steps.otelbot-token.outputs.token }} ================================================ FILE: .github/workflows/tidy-dependencies.yml ================================================ name: "Project: Tidy" on: pull_request_target: types: [opened, ready_for_review, synchronize, reopened, labeled, unlabeled] branches: - main permissions: read-all jobs: setup-environment: permissions: contents: write # for Git to git push timeout-minutes: 30 runs-on: ubuntu-latest if: ${{ !contains(github.event.pull_request.labels.*.name, 'dependency-major-update') && (github.actor == 'renovate[bot]' || contains(github.event.pull_request.labels.*.name, 'renovatebot')) && github.event.pull_request.head.repo.fork == false }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ github.head_ref }} - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version: oldstable cache: false - name: Cache Go id: go-cache uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: | ~/go/bin ~/go/pkg/mod key: go-cache-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/go.sum') }} - name: Install dependencies if: steps.go-cache.outputs.cache-hit != 'true' run: make -j2 gomoddownload - name: go mod tidy run: | make gotidy git config user.name otelbot git config user.email 197425009+otelbot@users.noreply.github.com echo "git diff --exit-code || (git add . && git commit -m \"go mod tidy\" && git push)" git diff --exit-code || (git add . && git commit -m "go mod tidy" && git push) - uses: actions-ecosystem/action-remove-labels@2ce5d41b4b6aa8503e285553f75ed56e0a40bae0 # v1.3.0 with: labels: renovatebot ================================================ FILE: .github/workflows/utils/cspell.json ================================================ { "version": "0.2", "language": "en", "words": [ "aggregatable", "Alolita", "Andrzej", "Anoshin", "Appy", "Autobuild", "Backpressure", "Baeyens", "Bebbington", "Biswal", "Bogdan", "Boten", "CHACHA", "CODEOWNERS", "Chao", "Chrs", "Confmap", "DOLLARSIGN", "Distro", "Dmitrii", "Dockerhub", "Dont", "Drutu", "Dynatrace", "Excalidraw", "Expvar", "Fanout", "FIPS", "Funcs", "GHSA", "GOARCH", "GOARM", "GOBIN", "GOCMD", "GOMEMLIMIT", "GOPATH", "GOPROXY", "Guiton", "HKLM", "Helmuth", "Hostfeature", "Islamov", "Jaglowski", "Janotti", "Juraci", "Karis", "Keepalive", "Koanf", "Kröhling", "MODSET", "Makwana", "Mapprovider", "Marshalers", "Marshallers", "Mathieu", "Mirabella", "Najaryan", "Nanos", "OTELBOT", "OTEP", "OTTL", "Olly", "Owais", "Paixão", "Pdata", "Prometheusremotewrite", "Punya", "RCPC", "Rahul", "SASL", "Samplingdecision", "Sharma", "Statefulness", "Stencel", "Strindices", "Tailsampling", "Tigran", "Toulme", "Triagers", "Unconfigured", "Unmarshable", "Unmarshal", "Unmarshalable", "Unmarshaller", "Unmarshallers", "unmarshalled", "Vihas", "Weng", "Zipkin", "adilhusain", "alibabacloudlogserviceexporter", "alives", "anchore", "andrzej", "apidiff", "atombender", "atoulme", "attributeprocessor", "attributesprocessor", "authextension", "authtest", "autobuild", "backoffs", "backpressure", "ballastextension", "batchprocessor", "bearertokenauthextension", "behaviour", "bogdandrutu", "braydonk", "bucketize", "buildinfo", "buildx", "bwalk", "capabilityconsumer", "certfile", "cgroupv", "checkapi", "checkdoc", "checklicense", "cheung", "chlog", "chloggen", "cmux", "codeboten", "codeowners", "componentalias", "componenterror", "componenthelper", "componentprofiles", "componentstatus", "componenttest", "configauth", "configcheck", "configcompression", "configcompressions", "configerror", "configgrpc", "confighttp", "configloader", "configmapprovider", "configmiddleware", "configmodels", "confignet", "configopaque", "configoptional", "configparser", "configretry", "configrpc", "configschema", "configsource", "configtelemetry", "configtest", "configtls", "configunmarshaler", "confmap", "confmaptest", "connectorprofiles", "connectortest", "consumerdata", "consumererror", "consumererrorprofiles", "consumerfanout", "consumerhelper", "consumerprofiles", "consumertest", "consumetest", "conv", "cookiejar", "coreinternal", "cpus", "cpuscraper", "crobert", "crosslink", "cumulativetodeltaprocessor", "customname", "dataloss", "datapoints", "debugexporter", "defaultcomponents", "dehaansa", "distro", "distros", "djaglowski", "dmathieu", "dmitryax", "dokey", "envmapprovider", "envprovider", "eventcreate", "exampleexporter", "examplereceiver", "expandconverter", "expandmapconverter", "expandvar", "exporterbatch", "exporterbatcher", "exporterhelper", "exporterhelperprofiles", "exporterprofiles", "exporterqueue", "exportertest", "exporthelper", "expvar", "extensionauth", "extensionauthtest", "extensioncapabilities", "extensionhelper", "extensionmiddleware", "extensionmiddlewaretest", "extensiontest", "extensionz", "fanout", "fanoutconsumer", "featureflags", "featuregate", "featuregates", "featurez", "fieldalignment", "fileexporter", "filemapprovider", "fileprovider", "filterprocessor", "filterset", "florianl", "fluentbit", "fluentforward", "forwardconnector", "fsnotify", "funcs", "gcflags", "genotelcorecol", "genpdata", "genproto", "goarm", "gobenchmark", "godoc", "gofmt", "gogenerate", "goimpi", "golangci", "goldendataset", "goleak", "golint", "gomod", "gomoddownload", "goporto", "goproxy", "goreleaser", "goroutines", "gotest", "gotidy", "govulncheck", "groupbyattrprocessor", "groupbytrace", "groupbytraceprocessor", "grpclb", "guiton", "healthcheck", "healthcheckextension", "healthcheckv", "hostcapabilities", "hostmetrics", "hostmetricsreceiver", "httpclientconfig", "httpprovider", "httpsprovider", "httptest", "illumos", "incorrectclass", "incorrectcomponent", "incorrectstability", "instrgen", "internaldata", "ints", "invalidaggregation", "invalidtype", "iruntime", "jaegerexporter", "jaegerreceiver", "jmacd", "jpkrohling", "jsoniter", "jsonpb", "kafkaexporter", "kafkaexporter's", "kafkareceiver", "keepalive", "koanf", "labeldrop", "ldflags", "limitermiddleware", "localhostgate", "loggingexporter", "logstest", "lycheeverse", "mapconverter", "mapstructure", "marshalers", "mdatagen", "mdatagen's", "memorylimiter", "memorylimiterextension", "memorylimiterprocessor", "metadatatest", "metricfamily", "metricreceiver", "metricsexporter", "metricsgenerationprocessor", "metricstransformprocessor", "middleware", "mostynb", "mowies", "muehle", "multiclient", "multimod", "mycert", "mycomponent", "myconnector", "myexporter", "myextension", "myorg", "myprocessor", "myreceiver", "myrepo", "mysite", "nodisplayname", "nonclobbering", "nopexporter", "nopreceiver", "nosuchprocessor", "notls", "obsreceiver", "obsreport", "obsreporttest", "oidcauthextension", "okey", "oldstable", "oltp", "omitempty", "omnition", "opencensus", "opencensusexporter", "opencensusreceiver", "otelbot", "otelcol", "otelcoltest", "otelconf", "otelconftelemetry", "otelcorecol", "otelgrpc", "otelhttp", "otelsvc", "oteltest", "otelzap", "otlpexporter", "otlpgrpc", "otlp_http", "otlphttp", "otlphttpexporter", "otlphttpexporter's", "otlphttpreceiver", "otlpjson", "otlpmetrics", "otlpreceiver", "otlptext", "overwritepropertiesconverter", "overwritepropertiesmapconverter", "parserprovider", "pcommon", "pdata", "pdatagen", "pdatagrpc", "perfcounters", "perflib", "pipelineprofiles", "pipelinez", "pjanotti", "plog", "plogotlp", "plogs", "pmetric", "pmetricotlp", "policyevaluation", "pprof", "pprofextension", "pprofile", "pprofiles", "pprofileotlp", "preconfigured", "priya", "probabilisticsamplerprocessor", "processorhelper", "processorhelperprofiles", "processorprofiles", "processortest", "processscraper", "proctelemetry", "prometheusexporter", "prometheusreceiver", "prometheusremotewrite", "prometheusremotewriteexporter", "protogen", "protos", "ptraceotlp", "queuebatch", "reaggregate", "receiverhelper", "receiverprofiles", "receivertest", "replicaset", "renovatebot", "resourcedetection", "resourcedetectionprocessor", "resourceprocessor", "resourcetolabel", "retryable", "riscv", "rpcz", "rrschulze", "runperf", "safelist", "sampleentity", "sampleentityreceiver", "samplefactoryreceiver", "samplereceiver", "samplingdecision", "samplingprocessor", "sarama", "sattributes", "sattributesprocessor", "schemagen", "scrapererror", "scraperhelper", "scrapertest", "semconv", "servicetelemetry", "servicetest", "servicez", "sfixed", "shanduur", "sharedcomponent", "sigstore", "someclientid", "someclientsecret", "somevalue", "songy", "spanmetricsconnector", "spanmetricsprocessor", "spanprocessor", "sprocessor", "statusdata", "statusreporting", "statuswatcher", "stdlib", "stencel", "stretchr", "subcomponent", "subcomponents", "subpackages", "swiatekm", "syft", "tailsampling", "tchannel", "telemetrygen", "telemetrytest", "testcomponents", "testconverter", "testdata", "testdesc", "testfunc", "testonly", "testprocessor", "testprovider", "testresults", "testutil", "tlsconfig", "tmpl", "tocstop", "tpmrm", "tracecontext", "traceid", "tracesonmemory", "tracetranslator", "tracez", "transformprocessor", "triager", "triagers", "triaging", "uints", "unixgram", "unixpacket", "unkeyed", "unmarshal", "unmarshalling", "unmarshalls", "unredacted", "unshallow", "unstarted", "userfriendly", "validatable", "vanityurl", "vmmetrics", "withauth", "xconfighttp", "xconfmap", "xconnector", "xconsumer", "xconsumererror", "xexporter", "xexporterhelper", "xextension", "xpdata", "xpipeline", "xprocessor", "xprocessorhelper", "xreceiver", "xscraper", "xscraperhelper", "yamlmapprovider", "yamlprovider", "yamls", "zapcore", "zipkin", "zipkinexporter", "zipkinreceiver", "zipkinv", "zpages", "zpagesextension", "zstd" ], "enableGlobDot": true, "useGitignore": true, "files": ["**/*.{md,yaml,yml}"], "globRoot": "../../..", "ignorePaths": [ ".git/*", ".git/!{COMMIT_EDITMSG,EDITMSG}", ".git/*/**", ".golangci.yml", ".github/**/*" ] } ================================================ FILE: .gitignore ================================================ bin/ dist/ /local .schemas # GoLand IDEA /.idea/ *.iml # VS Code .vscode/ .devcontainer/ # Cursor .cursor # Emacs *~ \#*\# # Miscellaneous files *.sw[op] *.DS_Store # Coverage coverage/* coverage.txt # Benchmarks **/benchmark.txt benchmarks.txt # Wix *.wixobj *.wixpdb # golang go.work* # npm (used for markdown-link-check) node_modules/* package-lock.json ================================================ FILE: .golangci.yml ================================================ formatters: enable: - gofumpt - goimports settings: gofmt: # simplify code: gofmt with `-s` option, true by default simplify: true gofumpt: # Choose whether to use the extra rules extra-rules: true goimports: # put imports beginning with prefix after 3rd-party packages; # it's a comma-separated list of prefixes local-prefixes: - go.opentelemetry.io/collector issues: # Maximum issues count per one linter. max-issues-per-linter: 0 # Maximum count of issues with the same text. max-same-issues: 0 linters: enable: - asasalint - contextcheck - copyloopvar - decorder - depguard - errcheck - errorlint - fatcontext - gocritic - gosec - govet - misspell - modernize - nolintlint - perfsprint - revive - staticcheck - testifylint - thelper - unconvert - unparam - unused - usestdlibvars - usetesting - whitespace exclusions: presets: - std-error-handling # Excluding configuration per-path, per-linter, per-text and per-source rules: [] # Log a warning if an exclusion rule is unused. warn-unused: true # all available settings of specific linters settings: depguard: rules: denied-deps: deny: - pkg: "go.uber.org/atomic" desc: "Use 'sync/atomic' instead of go.uber.org/atomic" - pkg: "gopkg.in/yaml.v3" desc: "Use 'go.yaml.in/yaml/v3' instead of gopkg.in/yaml.v3" - pkg: "github.com/pkg/errors" desc: "Use 'errors' or 'fmt' instead of github.com/pkg/errors" - pkg: "github.com/hashicorp/go-multierror" desc: "Use go.uber.org/multierr instead of github.com/hashicorp/go-multierror" - pkg: "math/rand$" desc: "Use the newer 'math/rand/v2' instead of math/rand" - pkg: "sigs.k8s.io/yaml" desc: "Use 'go.yaml.in/yaml' instead of sigs.k8s.io/yaml" semconv: list-mode: lax files: - "!cmd/mdatagen/**" # Exclude mdatagen deny: - pkg: go.opentelemetry.io/otel/semconv desc: Use go.opentelemetry.io/otel/semconv/v1.38.0 instead. If a newer semconv version has been released, update the depguard rule. allow: - go.opentelemetry.io/otel/semconv/v1.38.0 # Add a different guard rule so that we can ignore tests. ignore-in-test: # Allow in tests for testing pdata or other receivers/exporters that expect OTLP. files: - '!**/*_test.go' deny: - pkg: go.opentelemetry.io/proto desc: Use go.opentelemetry.io/collector/pdata instead gocritic: disabled-checks: - commentedOutCode - deferInLoop - filepathJoin - hugeParam - importShadow - rangeValCopy - unnamedResult - whyNoLint enable-all: true gosec: excludes: - G104 # FIXME - G402 - G404 govet: disable: # We want to order fields according to readability and grouping them by use cases. # This linter does not offer a discernible performance improvement as the structs # defined in this repository are not in the execution hot path. # See https://github.com/open-telemetry/opentelemetry-collector/issues/2789 - fieldalignment enable-all: true # settings per analyzer settings: printf: # analyzer name, run `go tool vet help` to see all analyzers funcs: # run `go tool vet help printf` to see available settings for `printf` analyzer - Infof - Warnf - Errorf - Fatalf misspell: # Correct spellings using locale preferences for US or UK. # Default is to use a neutral variety of English. # Setting locale to US will correct the British spelling of 'colour' to 'color'. locale: US ignore-rules: - cancelled nolintlint: require-specific: true perfsprint: # Optimizes even if it requires an int or uint type cast. int-conversion: true # Optimizes into `err.Error()` even if it is only equivalent for non-nil errors. err-error: true # Optimizes `fmt.Errorf`. errorf: true # Optimizes `fmt.Sprintf` with only one argument. sprintf1: true # Optimizes into strings concatenation. strconcat: true revive: # minimal confidence for issues, default is 0.8 confidence: 0.8 rules: # Blank import should be only in a main or test package, or have a comment justifying it. - name: blank-imports # context.Context() should be the first parameter of a function when provided as argument. - name: context-as-argument # Basic types should not be used as a key in `context.WithValue` - name: context-keys-type # Importing with `.` makes the programs much harder to understand - name: dot-imports - name: early-return arguments: - preserveScope # Empty blocks make code less readable and could be a symptom of a bug or unfinished refactoring. - name: empty-block # for better readability, variables of type `error` must be named with the prefix `err`. - name: error-naming # for better readability, the errors should be last in the list of returned values by a function. - name: error-return # for better readability, error messages should not be capitalized or end with punctuation or a newline. - name: error-strings # report when replacing `errors.New(fmt.Sprintf())` with `fmt.Errorf()` is possible - name: errorf # incrementing an integer variable by 1 is recommended to be done using the `++` operator - name: increment-decrement # highlights redundant else-blocks that can be eliminated from the code - name: indent-error-flow # This rule suggests a shorter way of writing ranges that do not use the second value. - name: range # receiver names in a method should reflect the struct name (p for Person, for example) - name: receiver-naming # redefining built in names (true, false, append, make) can lead to bugs very difficult to detect. - name: redefines-builtin-id # redundant else-blocks that can be eliminated from the code. - name: superfluous-else arguments: - preserveScope # prevent confusing name for variables when using `time` package - name: time-naming # warns when an exported function or method returns a value of an un-exported type. - name: unexported-return - name: unnecessary-stmt # spots and proposes to remove unreachable code. also helps to spot errors - name: unreachable-code # Functions or methods with unused parameters can be a symptom of an unfinished refactoring or a bug. - name: unused-parameter # Since Go 1.18, interface{} has an alias: any. This rule proposes to replace instances of interface{} with any. - name: use-any # report when a variable declaration can be simplified - name: var-declaration # warns when initialism, variable or package naming conventions are not followed. - name: var-naming staticcheck: checks: - all - -ST1000 - -ST1021 - -ST1022 testifylint: enable-all: true thelper: benchmark: begin: false fuzz: begin: false tb: begin: false test: begin: false # output configuration options output: # The formats used to render issues. formats: # Prints issues in a text format with colors, line number, and linter name. text: # Output path can be either `stdout`, `stderr` or path to the file to write to. path: stdout # print linter name in the end of issue text, default is true print-linter-name: true # print lines of code with issue, default is true print-issued-lines: true # Show statistics per linter. show-stats: false # options for analysis running run: # Allow multiple parallel golangci-lint instances running. # If false (default) - golangci-lint acquires file lock on start. allow-parallel-runners: true # default concurrency is a available CPU number concurrency: 4 # by default isn't set. If set we pass it to "go list -mod={option}". From "go help modules": # If invoked with -mod=readonly, the go command is disallowed from the implicit # automatic updating of go.mod described above. Instead, it fails when any changes # to go.mod are needed. This setting is most useful to check that go.mod does # not need updates, such as in a continuous integration and testing system. # If invoked with -mod=vendor, the go command assumes that the vendor # directory holds the correct copies of dependencies and ignores # the dependency descriptions in go.mod. modules-download-mode: readonly # exit code when at least one issue was found, default is 1 issues-exit-code: 1 # include test files or not, default is true tests: true # timeout for analysis, e.g. 30s, 5m, default is 1m timeout: 10m version: "2" ================================================ FILE: .markdownlint.yaml ================================================ # markdownlint configuration for OpenTelemetry Collector # https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md # # Start permissive to establish a baseline; tighten rules over time. default: true # Disabled rules (existing codebase has many violations) MD001: false # heading increment MD004: false # unordered list style MD007: false # unordered list indentation MD009: false # trailing spaces MD010: false # hard tabs MD012: false # multiple consecutive blank lines MD013: false # line length MD014: false # dollar signs in commands MD022: false # blanks around headings MD024: false # duplicate headings MD025: false # multiple top-level headings MD026: false # trailing punctuation in heading MD029: false # ordered list prefix MD030: false # spaces after list markers MD031: false # blanks around fences MD032: false # blanks around lists MD033: false # inline HTML MD034: false # bare URLs MD036: false # emphasis as heading MD038: false # spaces in code spans MD040: false # fenced code language MD041: false # first line heading MD047: false # single trailing newline MD049: false # emphasis style MD051: false # link fragments MD053: false # unused link definitions MD058: false # blanks around tables MD059: false # descriptive link text MD060: false # table column style MD018: false # no space after hash in heading MD028: false # blank line inside blockquote MD037: false # spaces inside emphasis ================================================ FILE: .markdownlintignore ================================================ # Changelogs (autogenerated, chloggen manages structure) CHANGELOG.md CHANGELOG-API.md # GitHub templates (intentional structure/HTML) .github/ ================================================ FILE: AGENTS.md ================================================ # AGENTS.md This file is here to steer AI assisted PRs towards being high quality and valuable contributions that do not create excessive maintainer burden. It is inspired by the Open Policy Agent and Fedora projects policies. ## General Rules and Guidelines The most important rule is not to post comments on issues or PRs that are AI-generated. Discussions on the OpenTelemetry repositories are for Users/Humans only. Follow the PR scoping guidance in [CONTRIBUTING.md](CONTRIBUTING.md). Keep AI-assisted PRs tightly isolated to the requested change and never include unrelated cleanup or opportunistic improvements unless they are strictly necessary for correctness. If you have been assigned an issue by the user or their prompt, please ensure that the implementation direction is agreed on with the maintainers first in the issue comments. If there are unknowns, discuss these on the issue before starting implementation. Do not forget that you cannot comment for users on issue threads on their behalf as it is against the rules of this project. ## Developer environment Make sure to follow docs/coding-guidelines.md on any contributions. Non-exhaustively, the important points are: * Whenever applicable, all code changes should have tests that actually validate the changes. ## Commit formatting We appreciate it if users disclose the use of AI tools when the significant part of a commit is taken from a tool without changes. When making a commit this should be disclosed through an Assisted-by: commit message trailer. Examples: ``` Assisted-by: ChatGPT 5.2 Assisted-by: Claude Opus 4.5 ``` ================================================ FILE: CHANGELOG-API.md ================================================ # Go API Changelog This changelog includes only developer-facing changes. If you are looking for user-facing changes, check out [CHANGELOG.md](./CHANGELOG.md). ## v1.54.0/v0.148.0 ### 🛑 Breaking changes 🛑 - `cmd/mdatagen`: Generate a per-metric config type `Config` for each metric when `reaggregation_enabled: true` (#14689) Metrics with aggregatable attributes get `AggregationStrategy` and `EnabledAttributes []AttributeKey` fields; others get `Enabled` only. Typed attribute key constants (e.g. `DefaultMetricAttributeKeyStringAttr`) replace `[]string`, eliminating runtime attribute slice allocations. Validation errors now list valid attributes and strategies by name. Components using `reaggregation_enabled: true` will need to update references to `MetricConfig`. - `pdata/pprofile`: Update protoID for message Sample fields (#14652) ### 💡 Enhancements 💡 - `cmd/mdatagen`: Extend mdatagen tool to generate go config structs for OpenTelemetry collector components. (#14561) The component config go code is generated from `config` section of the metadata.yaml file. - `cmd/mdatagen`: Extend mdatagen tool to generate config JSON schema for OpenTelemetry components. (#14543) The component config JSON schema is generated from `config` section of the metadata.yaml file. - `cmd/mdatagen`: Schema generation for mdatagen-controlled config parts (#14562) The schema is generated in separate yaml file and used to create full component schema. - `pdata/pprofile`: Implement reference based attributes in Profiles (#14546) - `pkg/pdata`: Upgrade the OTLP protobuf definitions to version 1.10.0 (#14766) - `pkg/service`: The internal status reporter no longer drops repeated Ok and RecoverableError statuses (#14282) Status events can now carry metadata and there's value in allowing them to be emitted despite the status value itself not changing. - `pkg/xexporterhelper`: Add logic to cleanup partitions from memory which are not being used for specific time period. (#14526) - `pkg/xpdata`: Add `NewEntity` constructor, `Entity.CopyToResource`, and `EntityAttributeMap.All` to `pdata/xpdata/entity` (#14659) ### 🧰 Bug fixes 🧰 - `cmd/mdatagen`: Preserve custom extensions (e.g., `x-customType`, `x-pointer`) during schema reference resolution. (#14713, #14565) Fixes an issue where custom properties defined locally on a node were overwritten and lost when resolving a `$ref` to an external/internal schema. - `pkg/xexporterhelper`: Fix race when partition is being removed from LRU and new items are being added at the same time. (#14526) ## v1.53.0/v0.147.0 ### 💡 Enhancements 💡 - `pkg/exporterhelper`: Add `metadata_keys` configuration to `sending_queue.batch.partition` to partition batches by client metadata (#14139) The `metadata_keys` configuration option is now available in the `sending_queue.batch.partition` section for all exporters. When specified, batches are partitioned based on the values of the listed metadata keys, allowing separate batching per metadata partition. This feature is automatically configured when using `exporterhelper.WithQueue()`. - `pkg/xexporterhelper`: Add code structure to handle unbounded partitions in sending queue. (#14526) ### 🧰 Bug fixes 🧰 - `pkg/config/configmiddleware`: Add context.Context to gRPC middleware interface constructors. (#14523) - `pkg/extensionmiddleware`: Add context.Context to gRPC middleware interface constructors. (#14523) This is a breaking API change for components that implement or use extensionmiddleware. ## v1.52.0/v0.146.1 ## v0.146.0 ### 🛑 Breaking changes 🛑 - `cmd/mdatagen`: Flatten the metric stability field (#14113) So we better match the weaver schema. Additional deprecation data can be set within the `deprecated` field. ### 🚩 Deprecations 🚩 - `pdata/pprofile`: Declare removed aggregation elements as deprecated. (#14528) ### 💡 Enhancements 💡 - `cmd/mdatagen`: Add entity association requirement for metrics and events when entities are defined (#14284) - `pkg/otelcol`: Gate process signals behind build tags (#14542) Particularly for Wasm on JS, there are no invalid process signal references, which would cause build failures. ## v1.51.0/v0.145.0 ### 💡 Enhancements 💡 - `pkg/config/configgrpc`: add client info to context before server authentication (#12836) - `pkg/xscraperhelper`: Add AddProfilesScraper similar to scraperhelper.AddMetricsScraper (#14427) ### 🧰 Bug fixes 🧰 - `pkg/config/configoptional`: Fix `Unmarshal` methods not being called when config is wrapped inside `Optional` (#14500) This bug notably manifested in the fact that the `sending_queue::batch::sizer` config for exporters stopped defaulting to `sending_queue::sizer`, which sometimes caused the wrong units to be used when configuring `sending_queue::batch::min_size` and `max_size`. As part of the fix, `xconfmap` exposes a new `xconfmap.WithForceUnmarshaler` option, to be used in the `Unmarshal` methods of wrapper types like `configoptional.Optional` to make sure the `Unmarshal` method of the inner type is called. The default behavior remains that calling `conf.Unmarshal` on the `confmap.Conf` passed as argument to an `Unmarshal` method will skip any top-level `Unmarshal` methods to avoid infinite recursion in standard use cases. ## v1.50.0/v0.144.0 ### 🛑 Breaking changes 🛑 - `pkg/config/confighttp`: Replace `ServerConfig.Endpoint` with `NetAddr confignet.AddrConfig`, enabling more flexible transport configuration. (#14187, #8752) This change adds "transport" as a configuration option, allowing users to specify different transport protocols (e.g., "tcp", "unix"). ### 🚩 Deprecations 🚩 - `pkg/scraperhelper`: Deprecate the `AddScraper` method. (#14428) ### 🚀 New components 🚀 - `pkg/xscraperhelper`: Add xscraperhelper for the experimental OTel profiling signal. (#14235) ### 💡 Enhancements 💡 - `all`: Add support for deprecated component type aliases (#14208) To add a deprecated type alias to a component factory, use the `WithDeprecatedTypeAlias` option. ```go return xexporter.NewFactory( metadata.Type, createDefaultConfig, xexporter.WithTraces(createTracesExporter, metadata.TracesStability), xexporter.WithDeprecatedTypeAlias("old_component_name"), ) ``` When the alias is used in configuration, a deprecation warning will be automatically logged, and the component will function normally using the original implementation. - `cmd/mdatagen`: Add the ability to disable attributes at the metric level and re-aggregate data points based off of these new dimensions (#10726) - `extension/xextension`: Add deprecated type alias support for extensions via `xextension` module (#14208) Extensions can now register deprecated type aliases using the experimental `xextension.WithDeprecatedTypeAlias` option. ```go return xextension.NewFactory( metadata.Type, createDefaultConfig, createExtension, metadata.Stability, xextension.WithDeprecatedTypeAlias("old_extension_name"), ) ``` When the alias is used in configuration, a deprecation warning will be automatically logged, and the extension will function normally using the original implementation. - `pkg/consumer/consumertest`: Add ProfileCount() (#14251) - `pkg/exporterhelper`: Add support for profile samples metrics (#14423) - `pkg/receiverhelper`: Add support for profile samples metrics (#14226) - `pkg/scraperhelper`: Introduce `AddMetricsScraper` to be more explicit than `AddScraper`. (#14428) - `receiver/otlp`: Add metrics tracking the number of receiver, refused and failed profile samples (#14226) ### 🧰 Bug fixes 🧰 - `pkg/xconnector`: Add component ID type validation to all xconnector Create methods (#14357) ## v1.49.0/v0.143.0 ### 🛑 Breaking changes 🛑 - `pkg/xprocessor`: Use pointer receivers in xprocessor factory methods for consistency with other factories. (#14348) ## v1.48.0/v0.142.0 ### 🛑 Breaking changes 🛑 - `pdata/xpdata`: Rename `Entity.IDAttributes()` to `Entity.IdentifyingAttributes()` and `Entity.DescriptionAttributes()` to `Entity.DescriptiveAttributes()` to align with OpenTelemetry specification terminology for attributes. (#14275) - `pkg/exporterhelper`: Use `configoptional.Optional` for the `exporterhelper.QueueBatchConfig` (#14155) It's recommended to change the field type in your component configuration to be `configoptional.Optional[exporterhelper.QueueBatchConfig]` to keep the `enabled` subfield. Use configoptional.Some(exporterhelper.NewDefaultQueueConfig()) to enable by default. Use configoptional.Default(exporterhelper.NewDefaultQueueConfig()) to disable by default. ### 🚩 Deprecations 🚩 - `pkg/service`: Deprecate Settings.LoggingOptions and telemetry.LoggerSettings.ZapOptions, add telemetry.LoggerSettings.BuildZapLogger (#14002) BuildZapLogger provides a more flexible way to build the Zap logger, since the function will have access to the zap.Config. This is used in otelcol to install a Windows Event Log output when the zap config does not specify any file output. ### 💡 Enhancements 💡 - `pdata/pprofile`: add ProfileCount() (#14239) ### 🧰 Bug fixes 🧰 - `pkg/confmap`: Ensure that embedded structs are not overwritten after Unmarshal is called (#14213) This allows embedding structs which implement Unmarshal and contain a configopaque.String. ## v1.47.0/v0.141.0 ### 🛑 Breaking changes 🛑 - `pkg/config/configgrpc`: Replace `component.Host` parameter of ToServer/ToClientConn by map of extensions (#13640) Components must now pass the map obtained from the host's `GetExtensions` method instead of the host itself. Nil may be used in tests where no middleware or authentication extensions are used. - `pkg/config/confighttp`: Replace `component.Host` parameter of ToServer/ToClient by map of extensions (#13640) Components must now pass the map obtained from the host's `GetExtensions` method instead of the host itself. Nil may be used in tests where no middleware or authentication extensions are used. ### 🚩 Deprecations 🚩 - `pkg/pdata`: Deprecate profile.Duration() and profile.SetDuration() (#14188) ### 💡 Enhancements 💡 - `pdata/pprofile`: Introduce `MergeTo` method (#14091) - `pkg/pdata`: Add profile.DurationNano() and profile.SetDurationNano() (#14188) ## v1.46.0/v0.140.0 ### 🛑 Breaking changes 🛑 - `pdata/pprofile`: Upgrade the OTLP protobuf definitions to version 1.9.0 (#14128) * Drop field `CommentStrindices` in `Profile`. * Rename `Sample` to `Samples` in `Profile`. * Rename `Line` to `Lines` in `Location`. * Remove `AggregationTemporality` field in `ValueType`. See https://github.com/open-telemetry/opentelemetry-proto/releases/tag/v1.9.0 - `pkg/otelcol`: The `otelcol.Factories.Telemetry` field is now required (#14003) Previously if this field was not set, then it would default to an otelconftelemetry factory. Callers of the otelcol package must now set the field explicitly. ### 💡 Enhancements 💡 - `pkg/pdata`: Upgrade the OTLP protobuf definitions to version 1.9.0 (#14128) ## v1.45.0/v0.139.0 ### 🛑 Breaking changes 🛑 - `all`: Change type of `configgrpc.ClientConfig.Headers`, `confighttp.ClientConfig.Headers`, and `confighttp.ServerConfig.ResponseHeaders` (#13930) `configopaque.MapList` is a new alternative to `map[string]configopaque.String` which can unmarshal both maps and lists of name/value pairs. For example, if `headers` is a field of type `configopaque.MapList`, then the following YAML configs will unmarshal to the same thing: ```yaml headers: "foo": "bar" headers: - name: "foo" value: "bar" ``` - `pdata/pprofile`: Update `SetFunction` to return the function's ID rather than update the Line (#14016, #14032) - `pdata/pprofile`: Update `SetLink` to return the link's ID rather than update the Sample (#14016, #14031) - `pdata/pprofile`: Update `SetMapping` to return the mapping's ID rather than update the Location (#14016, #14030) - `pkg/otelcol`: Require a telemetry factory to be injected through otelcol.Factories (#4970) otelcol.Factories now has a required Telemetry field, which contains the telemetry factory to be used by the service. Set it to otelconftelemetry.NewFactory() for the existing behavior. - `pkg/pdata`: Remove unused generated code from pprofile (#14073) Experimental package, ok to break since not used. ### 💡 Enhancements 💡 - `pdata/pprofile`: Introduce `SetStack` method (#14007) - `pdata/xpdata`: Add high-level Entity API for managing entities attached to resources (#14042) Introduces `Entity`, `EntitySlice`, and `EntityAttributeMap` types that provide a user-friendly interface for working with resource entities. The new API ensures consistency between entity and resource attributes by sharing the underlying attribute map, and prevents attribute conflicts between entities. This API may eventually replace the generated protobuf-based API for better usability. ### 🧰 Bug fixes 🧰 - `cmd/mdatagen`: Fix mdatagen generated_metrics for connectors (#12402) ## v1.44.0/v0.138.0 ### 🛑 Breaking changes 🛑 - `pkg/xexporterhelper`: Remove definition of Sizer from public API and ability to configure. (#14001) Now that Request has both Items/Bytes sizes no need to allow custom sizers. - `pkg/service`: The `service.Settings` type now requires a `telemetry.Factory` to be provided (#4970) ### 🚩 Deprecations 🚩 - `pdata/pprofile`: Deprecated `PutAttribute` helper method (#14016, #14041) - `pdata/pprofile`: Deprecated `PutLocation` helper method (#14019) ### 💡 Enhancements 💡 - `all`: Add `keep_alives_enabled` option to ServerConfig to control HTTP keep-alives for all components that create an HTTP server. (#13783) - `pkg/pdata`: Add pcommon.Map helper to add a key to the map if does not exists (#14023) - `pdata/pprofile`: Introduce `Equal` method on the `KeyValueAndUnit` type (#14041) - `pkg/pdata`: Add `RemoveIf` method to primitive slice types (StringSlice, Int64Slice, UInt64Slice, Float64Slice, Int32Slice, ByteSlice) (#14027) - `pdata/pprofile`: Introduce `SetAttribute` helper method (#14016, #14041) - `pdata/pprofile`: Introduce `SetLocation` helper method (#14019) - `pdata/pprofile`: Introduce `Equal` method on the `Stack` type (#13952) ## v1.43.0/v0.137.0 ### 🛑 Breaking changes 🛑 - `pkg/exporterhelper`: Remove all experimental symbols in exporterhelper (#11143) They have all been moved to xexporterhelper ### 🚩 Deprecations 🚩 - `all`: service/telemetry.TracesConfig is deprecated (#13904) This type alias has been added to otelconftelemetry.TracesConfig, where the otelconf-based telemetry implementation now lives. ### 💡 Enhancements 💡 - `all`: Mark configoptional as stable (#13403) - `all`: Mark configauth module as 1.0 (#9476) - `pkg/pdata`: Mark featuregate pdata.useCustomProtoEncoding as stable (#13883) ## v1.42.0/v0.136.0 ### 🛑 Breaking changes 🛑 - `exporterhelper`: Remove deprecated function NewRequestsSizer (#13803) - `pdata/pprofile`: Upgrade the OTLP protobuf definitions to version 1.8.0 (#13758, #13825, #13839) - `pdata/pprofile`: Remove deprecated ProfilesDictionary method (#13858) ### 🚩 Deprecations 🚩 - `exporterhelper`: Deprecate all experimental symbols in exporterhelper and move them to xexporterhelper (#11143) ### 💡 Enhancements 💡 - `configoptional`: Add `GetOrInsertDefault` method to `configoptional.Optional` (#13856) This method inserts a default or zero value into a `None`/`Default` `Optional` before `Get`ting its inner value. - `exporter`: Stabilize exporter module. (#12978) This does not stabilize the exporterhelper module or configuration - `pdata`: Upgrade the OTLP protobuf definitions to version 1.8.0 (#13758) ## v1.41.0/v0.135.0 ### 🛑 Breaking changes 🛑 - `pdata/pprofile`: Remove deprecated AddAttribute method (#13764) ### 💡 Enhancements 💡 - `configmiddleware`: Stabilize `configmiddleware` module (#13422) This only stabilizes the configuration interface but does not stabilize the middlewares themselves or the way of implementing them. - `xpdata`: Add experimental MapBuilder struct to optimize pcommon.Map construction (#13617) ## v1.40.0/v0.134.0 ### 💡 Enhancements 💡 - `exporterhelper`: Split exporterhelper into a separate module (#12985) ## v1.39.0/v0.133.0 ### 🛑 Breaking changes 🛑 - `configgrpc`: Set `tcp` as the default transport type (#13657) gRPC is generally used with HTTP/2, so this will simplify usage for most components. ### 🚩 Deprecations 🚩 - `pdata/pprofile`: Deprecate Profiles.ProfilesDictionary in favor of Profiles.Dictionary. (#13644) ### 💡 Enhancements 💡 - `pdata`: Add support for local memory pooling for data objects. (#13678) This is still an early experimental (alpha) feature. Do not recommended to be used production. To enable use "--featuregate=+pdata.useProtoPooling" ### 🧰 Bug fixes 🧰 - `configoptional`: Allow validating nested types (#13579) `configoptional.Optional` now implements `xconfmap.Validator` ## v1.38.0/v0.132.0 ### 🛑 Breaking changes 🛑 - `componenttest`: Remove `GetFactory` from the host returned by `NewNopHost` (#13577) This method is no longer part of the `component.Host` interface. ### 💡 Enhancements 💡 - `exporterhelper`: Provide an interface `queue_batch.Setting.MergeCtx` so users can control how context values are preserved or combined (#13320) By supplying a custom mergeCtx function, users can control how context values are preserved or combined. The default behavior is to preserve no context values. - `pdata`: Generate Logs/Traces/Metrics/Profiles and p[log|trace|metric|profile]ExportResponse with pdatagen. (#13597) This change brings consistency on how these structs are written and remove JSON marshaling/unmarshaling hand written logic. - `pdata`: Avoid unnecessary buffer copy when JSON marshal fails. (#13598) - `pipeline`: Mark module as stable (#12831) ## v1.37.0/v0.131.0 ### 🛑 Breaking changes 🛑 - `configgrpc`: Update optional fields to use `configoptional.Optional` field for optional values. (#13252, #13364) Specifically, the following fields have been updated to `configoptional`: - `KeepaliveServerConfig.ServerParameters` (`KeepaliveServerParameters` type) - `KeepaliveServerConfig.EnforcementPolicy` (`KeepaliveEnforcementPolicy` type) - `xexporterhelper`: Remove deprecated NewProfilesExporter function from xexporterhelper package (#13391) ### 💡 Enhancements 💡 - `consumererror`: Add new "Downstream" error marker (#13234) This new error wrapper type indicates that the error returned by a component's `Consume` method is not an internal failure of the component, but instead was passed through from another component further downstream. This is used internally by the new pipeline instrumentation feature to determine the `outcome` of a component call. This wrapper is not intended to be used by components directly. - `pdata/pprofile`: Introduce `Equal` method on the `Function` type (#13222) - `pdata/pprofile`: Introduce `Equal` method on the `Link` type (#13223) - `pdata/pprofile`: Add new helper method `SetFunction` to set a new function on a line. (#13222) - `pdata/pprofile`: Add new helper method `SetLink` to set a new link on a sample. (#13223) - `pdata/pprofile`: Add new helper method `SetString` to set or retrieve the index of a value in the StringTable. (#13225) ## v1.36.1/v0.130.1 No API-only changes in this release. ## v1.36.0/v0.130.0 ### 🛑 Breaking changes 🛑 - `exporterhelper`: Use configoptional for sending_queue::batch field (#13345) - `configgrpc`: Update optional fields to use `configoptional.Optional` field for optional values. (#13250, #13252) Components using `configgrpc` package may need to update config values. - `confighttp`: Use configoptional.Optional in confighttp (#9478) - `exporterhelper`: Remove sizer map in favor of items/bytes sizers. Request based is automatically supported. (#13262) - `pdata/pprofile`: Remove field Profile.StartTime from pdata/pprofile (#13315) Remove Profile.StartTime from OTel Profiling signal. - `exporterhelper`: Remove deprecated old batcher config (#13003) - `exporter/otlp`: Remove deprecated batcher config from OTLP, use queuebatch (#13339) ### 🚩 Deprecations 🚩 - `exporterhelper`: Deprecate NewRequestsSizer always supported. (#13262) - `xexporterhelper`: Introduce NewProfiles method and deprecate NewProfilesExporter (#13372) ### 💡 Enhancements 💡 - `consumererror`: Add `Error` type (#7047) This type can contain information about errors that allow components (e.g. exporters) to communicate error information back up the pipeline. - `pdata`: Document that changing pcommon.Map (Remove/removeIf/Put*) invalidates Value references obtained via Get. (#13073) - `cmd/mdatagen`: Add support for optional attribute (#12571) - `exporterhelper`: Add support to configure a different Sizer for the batcher than the queue (#13313) - `pdata`: Add support for the new resource-entity reference API as part of the experimental xpdata package. (#13264) ## v1.35.0/v0.129.0 ### 🛑 Breaking changes 🛑 - `semconv`: Removing deprecated semconv package (#13071) - `configgrpc,confighttp`: Unify return type of `NewDefault*Config` functions to return a struct instead of a pointer. (#13169) - `exporterhelper`: QueueBatchEncoding interface is changed to support marshaling and unmarshaling of request context. (#13188) ### 💡 Enhancements 💡 - `pdata/pprofile`: Introduce `Equal` method on the `Mapping` type (#13197) - `configoptional`: Make unmarshaling into `None[T]` work the same as unmarshaling into `(*T)(nil)`. (#13168) - `configoptional`: Add a confmap.Marshaler implementation for configoptional.Optional (#13196) - `pdata/pprofile`: Introduce `Equal` methods on the `Line` and `Location` types (#13150) - `pdata/pprofile`: Add new helper method `SetMapping` to set a new mapping on a location. (#13197) ### 🧰 Bug fixes 🧰 - `confmap`: Distinguish between empty and nil values when marshaling `confmap.Conf` structs. (#13196) ## v1.34.0/v0.128.0 ### 🛑 Breaking changes 🛑 - `exporterhelper`: Remove deprecated NewProfilesRequestExporter function from xexporterhelper package (#13157) - `confighttp`: Remove pointer to field `cookies` in confighttp.ClientConfig (#13116) - `otlpreceiver`: Use `configoptional.Optional` to define optional configuration sections in the OTLP receiver. Remove `Unmarshal` method. (#13119) - `confighttp,configgrpc`: Rename `ClientConfig.TLSSetting` and `ServerConfig.TLSSetting` to `ClientConfig.TLS` and `ServerConfig.TLS`. (#13115) - `pdata/pprofile`: Upgrade the OTLP protobuf definitions to version 1.7.0 (#13075) Note that the batcher is temporarily a noop. - `pipeline`: Remove deprecated MustNewID[WithName] (#13139) ### 🚀 New components 🚀 - `configoptional`: Add a new configoptional module to support optional configuration fields. (#12981) ### 💡 Enhancements 💡 - `pdata`: Introduce `MoveAndAppendTo` methods to the generated primitive slices (#13074) - `pdata`: Upgrade the OTLP protobuf definitions to version 1.7.0 (#13075) ### 🧰 Bug fixes 🧰 - `confmap`: Correctly distinguish between `nil` and empty map values on the `ToStringMap` method (#13161) This means that `ToStringMap()` method can now return a nil map if the original value was `nil`. If you were not doing so already, make sure to check for `nil` before writing to the map to avoid panics. - `confighttp`: Make the `NewDefaultServerConfig` function return a nil TLS config by default. (#13129) - The previous default was a TLS config with no certificates, which would fail at runtime. ## v1.33.0/v0.127.0 ### 🛑 Breaking changes 🛑 - `mdatagen`: Add context parameter for recording event to set traceID and spanID (#12571) - `otlpreceiver`: Use wrapper type for URL paths (#13046) ### 🚩 Deprecations 🚩 - `pipeline`: Deprecate MustNewID and MustNewIDWithName (#12831) - `pdata/profile`: Replace AddAttribute with the PutAttribute helper method to modify the content of attributable records. (#12798) ### 💡 Enhancements 💡 - `consumer/consumertest`: Add context to sinks (#13039) - `cmd/mdatagen`: Add events in generated documentation (#12571) - `confmap`: Add a `Conf.Delete` method to remove a path from the configuration map. (#13064) - `confmap`: Support running Unmarshal hooks on nil values. (#12981) ## v1.32.0/v0.126.0 ### 🚩 Deprecations 🚩 - `configauth`: Deprecate `configauth.Authentication` in favor of `configauth.Config`. (#12875) ### 💡 Enhancements 💡 - `cmd/mdatagen`: Add type definition for events in mdatagen (#12571) - `cmd/mdatagen`: Add functions for processing structured events in mdatagen (#12571) ## v1.31.0/v0.125.0 ### 🚩 Deprecations 🚩 - `extensionauthtest`: Deprecate NewErrorClient in favor of NewErrClient. (#12874) ### 💡 Enhancements 💡 - `xextension/storage`: ErrStorageFull error added to xextension/storage contract (#12925) - `pdata`: Add MoveTo to pcommon.Value, only type missing this (#12877) ### 🧰 Bug fixes 🧰 - `pdata`: Fix MoveTo when moving to the same destination (#12887) ## v1.30.0/v0.124.0 ### 🛑 Breaking changes 🛑 - `exporterbatcher`: Remove deprecated package exporterbatcher (#12780) - `exporterqueue`: Remove deprecated package exporterqueue (#12779) ### 💡 Enhancements 💡 - `mdatagen`: Add variable for metric name in mdatagen (#12459) Access metric name via `metadata.MetricsInfo..Name` - `client`: Add support for iterating over client metadata keys (#12804) - `service`: Adds the GetFactory interface to the hostcapabilities package (#12789) - `cmd/mdatagen`: Add the foundational changes necessary for supporting logs data in `mdatagen` (#12571) ## v1.29.0/v0.123.0 ### 🛑 Breaking changes 🛑 - `otlpreceiver/otlpexporter/otlphttpexporter`: Avoid using go embedded messages in Config (#12718) - `exporterqueue`: Move Queue interface to internal, disallow alternative implementations (#12680) - `extensionauth, configauth`: Remove deprecated types and functions from `extensionauth` and `configauth` packages. (#12672) This includes: - `extensionauth.NewClient`, - `extensionauth.ClientOption` and all its implementations, - `extensionauth.NewServer`, - `extensionauth.ServerOption` and all its implementations and - `configauth.Authenticator.GetClientAuthenticator`. - `exporterhelper`: Remove deprecated converter types from exporterhelper (#12686) - `exporterbatch`: Remove deprecated fields `min_size_items` and `max_size_items` from batch config. (#12684) ### 🚩 Deprecations 🚩 - `exporterhelper`: Deprecate BatcherConfig, SizeConfig and WithBatcher in favor of the new QueueBatchConfig. (#12748) - `exporterbatcher`: Deprecated Config, SizeConfig, SizerType, SizerType[Requests|Items|Bytes], NewDefaultConfig. Use alias from exporterhelper. (#12707) - `exporterqueue`: Deprecated Config, NewDefaultConfig, Encoding, ErrQueueFull. Use alias from exporterhelper. (#12706) - `exporterhelper`: Deprecate exporterhelper WithRequestQueue in favor of WithQueueBatch (#12679) - `exporterhelper`: Deprecate `QueueConfig` in favor of `QueueBatchConfig`. (#12746) ### 💡 Enhancements 💡 - `extensionauth`: Mark module as stable (#11006) - `processor`: Mark module as stable. (#12677) - `processorhelper`: Split processorhelper into a separate module. (#12678) ## v1.28.1/v0.122.1 ## v1.28.0/v0.122.0 ### 🛑 Breaking changes 🛑 - `auth, authtest`: Remove deprecated modules extension/auth and extension/auth/authtest (#12543) Use extension/extensionauth and extension/extensionauth/extensionauthtest instead. - `extensionauth`: Remove deprecated methods from the `Func` types. (#12547) - `extensiontest, connectortest, processortest, receivertest, scrapertest, exportertest`: Remove deprecated `NewNopSettingsWithType` functions, use `NewNopSettings` instead. (#12221) - `extensionauthtest`: Remove the `extensionauthtest.MockClient` struct. (#12567) - Use `extensionauthtest.NewNopClient` to create a client with a noop implementation. - Use `extensionauthtest.NewErrorClient` to create a client that always returns an error. - Implement the `extensionauth` interfaces for custom mock client implementations. - `component/componenttest`: Remove the deprecated componenttest.TestTelemetry in favor of componenttest.Telemetry (#12419) - `exporterhelper`: Remove the Request.Export function in favor of an equivalent request consume func in the New[Traces|Metrics|Logs|Profiles]Request (#12637) ### 🚩 Deprecations 🚩 - `exporterhelper`: Deprecate per signal converter in favor of generic version (#12631) - `extensionauth`: Deprecate `extensionauth.NewClient` and `extensionauth.NewServer`. (#12574) - Manually implement the interfaces instead. - `configauth`: Deprecate `configauth.Authenticator.GetClientAuthenticator`. (#12574) - Use the per-protocol methods instead. ### 🚀 New components 🚀 - `receiverhelper`: Split `receiverhelper` into a separate module (#28328) ### 💡 Enhancements 💡 - `cmd/mdatagen`: Add `supportsSignal` func for `Metadata` type in `mdatagen`. (#12640) - `receiver`: Mark module as stable (#12513) - `pdata/pcommon`: Introduce `Equal()` method for comparison equality to `Value`, `ByteSlice`, `Float64Slice`, `Int32Slice`, `Int64Slice`, `StringSlice`, `Uint64Slice`, `Map` and `Slice` (#12594) - `pdata`: Add iterator All method to pdata slices and map types. (#11982) - `pdata/pprofile`: Introduce AddAttribute helper method to modify the content of attributable records (#12206) ## v1.27.0/v0.121.0 ### 🛑 Breaking changes 🛑 - `exporterqueue`: Remove exporterqueue.Factory in favor of the NewQueue function, and merge configs for memory and persistent. (#12509) As a side effect of this change, no alternative implementation of the queue are supported and the Queue interface will be hidden. - `exporterhelper`: Update MergeSplit function signature to use the new SizeConfig (#12486) - `extension, connector, processor, receiver, exporter, scraper`: Remove deprecated `Create*` methods from `Create*Func` types. (#12305) The `xconnector.CreateMetricsToProfilesFunc.CreateMetricsToProfiles` method has been removed without a deprecation. - `component`: Remove deprecated function and interface `ConfigValidator` and `ValidateConfig`. (#11524) - Use `xconfmap.Validator` and `xconfmap.Validate` instead. - `receiver, scraper, processor, exporter, extension`: Remove deprecated MakeFactoryMap functions in favor of generic implementation (#12222) - `exporterhelper`: Change the signature of the exporterhelper.WithQueueRequest to accept Encoding instead of the Factory. (#12509) - `component/componenttest`: Removing the deprecated `CheckReceiverMetrics` and `CheckReceiverTraces` functions. (#12185) ### 🚩 Deprecations 🚩 - `componenttest`: Deprecated componenttest.TestTelemetry in favor of componenttest.Telemetry (#12419) - `connector, exporter, extension, processor, receiver, scraper`: Add type parameter to `NewNopSettings` and deprecate `NewNopSettingsWithType` (#12305) - `exporterhelper`: Deprecate MinSizeConfig and MaxSizeItems. (#12486) - `extension/extensionauth`: Deprecate methods on `*Func` types. (#12480) - `extension/auth, extension/auth/authtest`: Deprecate extension/auth and the related test module in favor of extension/extensionauth (#12478) ### 🚀 New components 🚀 - `service/hostcapabilities`: create `service/hostcapabilities` module (#12296, #12375) Removes getExporters interface in service/internal/graph. Removes getModuleInfos interface in service/internal/graph. Creates interface ExposeExporters in service/hostcapabilities to expose GetExporters function. Creates interface ModuleInfo in service/hostcapabilities to expose GetModuleInfos function. ### 💡 Enhancements 💡 - `exporterhelper`: Adds the config API to support serialized bytes based batching (#3262) - `configauth`: Add the `omitempty` mapstructure tag to struct fields (#12191) This results in unset fields not being rendered when marshaling. - `confighttp`: Add the `omitempty` mapstructure tag to struct fields (#12191) This results in unset fields not being rendered when marshaling. - `otelcol`: Converters are now available in the `components` command. (#11900, #12385) - `extension`: Mark module as stable (#11005) - `pcommon.Map`: preallocate go map in Map.AsRaw() (#12406) - `exporterhelper`: Stabilize exporter.UsePullingBasedExporterQueueBatcher and remove old batch sender (#12425) - `service`: Add the `omitempty` mapstructure tag to struct fields (#12191) This results in unset fields not being rendered when marshaling. ### 🧰 Bug fixes 🧰 - `mdatagen`: Fix broken imports in the generated files. (#12298) - `processor, connector, exporter, receiver`: Explicitly error out at component creation time if there is a type mismatch. (#12305) ## v1.26.0/v0.120.0 ### 🛑 Breaking changes 🛑 - `configauth`: Remove NewDefaultAuthentication (#12223) The value returned by this function will always cause an error on startup. In `configgrpc.Client/ServerConfig.Auth`, `nil` should be used instead to disable authentication. - `otelcol`: Make the `ConfigProvider` interface a struct (#12297) Calls to `NewConfigProvider` will now return `*ConfigProvider`, but will otherwise work the same as before. - `extension`: Remove `extension.Settings.ModuleInfo` (#12296) - The functionality is now available as an optional, hidden interface on `service`'s implementation of the `Host` - `component`: Remove deprecated field `component.TelemetrySettings.MetricsLevel`. (#11061) - `confighttp`: Add `ToClientOption` type and add it to signature of `ToClient` method. (#12353) - This has no use for now, it may be used in the future. - `mdatagen`: Remove unused not_component config for mdatagen (#12237) ### 🚩 Deprecations 🚩 - `component/componenttest`: Deprecate CheckReceiverMetrics in componenttest (#12185) Use the `metadatatest.AssertEqualMetric` series of functions instead of `obsreporttest.CheckReceiverMetrics` - `component/componenttest`: Deprecate CheckReceiverTraces in componenttest (#12185) Use the `metadatatest.AssertEqualMetric` series of functions instead of `obsreporttest.CheckReceiverTraces` - `component`: Deprecate `ConfigValidator` and `ValidateConfig` (#11524) Please use `Validator` and `Validate` respectively from `xconfmap`. - `receiver, scraper, processor, exporter, extension`: Deprecate existing MakeFactoryMap functions in favor of generic implementation (#12222) - `extension, connector, processor, receiver, exporter, scraper`: Deprecate `Create*` methods from `Create*Func` types. (#12305) - `extensiontest, connectortest, processortest, receivertest, exportertest, scrapertest`: Deprecate `*test.NewNopSettings` in favor of `*test.NewNopSettingsWithType` (#12305) ### 🚀 New components 🚀 - `xconfmap`: Create the xconfmap module and add the `Validator` interface and `Validate` function to facilitate config validation (#11524) ### 💡 Enhancements 💡 - `configgrpc`: Add the `omitempty` mapstructure tag to struct fields (#12191) This results in unset fields not being rendered when marshaling. - `confignet`: Add the `omitempty` mapstructure tag to struct fields (#12191) This results in unset fields not being rendered when marshaling. - `configtls`: Add the `omitempty` mapstructure tag to struct fields (#12191) This results in unset fields not being rendered when marshaling. - `consumer`: Clarify that data cannot be accessed after Consume* func is called. (#12284) - `pdata/pprofile`: Introduce aggregation temporality constants (#12253) ### 🧰 Bug fixes 🧰 - `configgrpc`: Apply configured Headers automatically (#12307) configgrpc now calls metadata.AppendToOutgoingContext automatically in an interceptor. Components that were manually using metadata.NewOutgoingContext as a workaround no longer need to do so, unless they are overwriting or adding header keys. - `configgrpc`: Set Auth to nil in NewDefaultClientConfig/NewDefaultServerConfig (#12223) The value that was used previously would always cause an error on startup. - `exporterqueue`: Fix async queue to propagate cancellation all they way to the queue (#12282) - `otlpreceiver`: Fix OTLP http receiver to correctly set Retry-After (#12367) - `extension`: Explicitly error out at extension creation time if there is a type mismatch. (#12305) ## v1.25.0/v0.119.0 ### 🛑 Breaking changes 🛑 - `exporterhelper`: Change queue to embed the async consumers. (#12242) - `exporterqueue`: Change Queue interface to return a callback instead of an index (#8122) - `cmd/mdatagen`: Allow passing OTel Metric SDK options to the generated `SetupTelemetry` function. (#12166) - `exporterhelper`: Rename exporter span signal specific attributes (e.g. "sent_spans" / "send_failed_span") to "items.sent" / "items.failed". (#12165) - `component`: Change underlying type for `component.Kind` to be a struct. (#12214) - `extension`: Change `extension.Extension` to be an interface that embeds `component.Component` instead of an alias (#11443) - `component/componenttest`: Remove deprecated `CheckScraperMetrics` functions (#12183) - `scraperhelper`: Remove deprecated ScrapperControllerOption and NewScraperControllerMetrics from scraperhelper. (#12147) ### 🚩 Deprecations 🚩 - `metadatatest`: Deprecate metadatatest.Telemetry in favor of componenttest.Telemetry (#12218) metadatatest.Telemetry -> componenttest.Telemetry | metadatatest.SetupTelemetry -> componenttest.NewTelemetry | metadatatest.Telemetry.NewSettings -> metadatatest.NewSettings | metadatatest.Telemetry.AssertMetrics -> metadatatest.AssertEqual* | - `component/componenttest`: Deprecate `CheckExporterEnqueue*` functions in componenttest (#12185) Use the `metadatatest.AssertEqualMetric` series of functions instead of `obsreporttest.CheckExporterEnqueue*` functions. - `component/componenttest`: Deprecate CheckExporterLogs in componenttest (#12185) Use the `metadatatest.AssertEqualMetric` series of functions instead of `obsreporttest.CheckExporterLogs` - `component/componenttest`: Deprecate CheckExporterMetricGauge in componenttest (#12185) Use the `metadatatest.AssertEqualMetric` series of functions instead of `obsreporttest.CheckReceiverMetricGauge` - `component/componenttest`: Deprecate CheckExporterMetrics in componenttest (#12185) Use the `metadatatest.AssertEqualMetric` series of functions instead of `obsreporttest.CheckExporterMetrics` - `component/componenttest`: Deprecate CheckExporterTraces in componenttest (#12185) Use the `metadatatest.AssertEqualMetric` series of functions instead of `obsreporttest.CheckExporterTraces` - `component/componenttest`: Deprecate CheckReceiverLogs in componenttest (#12185) Use the `metadatatest.AssertEqualMetric` series of functions instead of `obsreporttest.CheckReceiverLogs` - `mdatagen`: Make registration of callback for async metric always optional. (#12204) Deprecate `metadata.TelemetryBuilder.Init*` and `metadata.With*Callback` in favor of `metadata.TelemetryBuilder.Register*Callback` - `component`: Deprecate `component.TelemetrySettings.MetricsLevel` in favor of using views and 'Enabled' method. (#12159) - Components will temporarily need the service to support using views. ### 💡 Enhancements 💡 - `componenttest`: Add helper to get a metric for componenttest.Telemetry (#12215) - `componenttest`: Extract componenttest.Telemetry as generic struct for telemetry testing (#12151) - `mdatagen`: Generate assert function for each metric in mdatagen (#12179) - `metadatatest`: Generate NewSettings that accepts componenttest.Telemetry (#12216) - `pdata/pprofile`: Add new helper method `FromAttributeIndices` to build a `pcommon.Map` out of `AttributeIndices`. (#12176) - `scraper`: Support logs scraper (#12116) - `component`: Allow `component.ValidateConfig` to recurse through all fields in a config object (#11524) - `component`: Show path to invalid config in errors returned from `component.ValidateConfig` (#12108) ### 🧰 Bug fixes 🧰 - `mdatagen`: All register callbacks to async instruments can now be unregistered by calling `metadata.TelemetryBuilder.Shutdown()` (#12204) - `mdatagen`: Fix bug where Histograms were marked as not supporting temporal aggregation (#12168) ## v1.24.0/v0.118.0 ### 🛑 Breaking changes 🛑 - `exporterqueue`: Change Queue Size and Capacity to return explicit int64. (#12076) - `receiver/scraperhelper`: Removing the deprecated receiver/scraperhelper package (#12054) - `processortest`: Revert the nop_processor.NewNopSettings change, as it is no longer needed (#11433) - `experimental/storage`: Remove deprecated package/module experimental/storage (#12109) - `mdatagen`: Remove deprecated generated_component_telemetry_test file from being generated and delete it. (#12068) - `receivertest`: Remove deprecated receivertest.NewNopFactoryForType (#12110) ### 🚩 Deprecations 🚩 - `componenttest`: Deprecate CheckScraperMetrics in componenttest (#12105) Use `metadatatest.AssertMetrics` instead of `obsreporttest.CheckScraperMetrics` - `scraperhelper`: Deprecate `scraperhelper.NewScraperControllerReceiver` and `scraperhelper.ScraperControllerOption`. (#12103) Use `scraperhelper.NewMetricsController` instead of `scraperhelper.NewScraperControllerReceiver` | Use `scraperhelper.ScraperControllerOption` instead of `scraperhelper.ControllerOption` ### 💡 Enhancements 💡 - `exporterhelper`: Add capability for memory and persistent queue to block when add items (#12074) - `scraper/scraperhelper`: Add obs_logs for scraper/scraperhelper (#12036) This change adds obs for logs in scraper/scraperhelper, also introduced new metrics for scraping logs. - `mdatagen`: Add scraper component type support to mdatagen (#12092) - `mdatagen`: Add tracing support in metadatatest (#12106) - `exporterhelper`: Change persistent queue to not use sized channel, improve memory usage and simplify sized_channel. (#12060) - `confighttp`: Added support for configuring compression levels. (#10467) A new configuration option called CompressionParams has been added to confighttp. | This allows users to configure the compression levels for the confighttp client. ## v1.23.0/v0.117.0 ### 🛑 Breaking changes 🛑 - `pdata/pprofile`: Remove duplicate Attributes field from profile (#11932) - `connector`: Remove deprecated connectorprofiles module, use xconnector instead. (#11778) - `consumererror`: Remove deprecated consumererrorprofiles module, use xconsumererror instead. (#11778) - `consumer`: Remove deprecated consumerprofiles module, use xconsumer instead. (#11778) - `exporterhelper`: Remove deprecated exporterhelperprofiles module, use xexporterhelper instead. (#11778) - `exporter`: Remove deprecated exporterprofiles module, use xexporter instead. (#11778) - `pipeline`: Remove deprecated pipelineprofiles module, use xpipeline instead. (#11778) - `processorhelper`: Remove deprecated processorhelperprofiles module, use xprocessorhelper instead. (#11778) - `processor`: Remove deprecated processorprofiles module, use xprocessor instead. (#11778) - `receiver`: Remove deprecated receiverprofiles module, use xreceiver instead. (#11778) - `exporterhelper`: Remove Merge function from experimental Request interface (#12012) ### 🚩 Deprecations 🚩 - `mdatagen`: Deprecate component_test in favor of metadatatest (#11812) - `receivertest`: Deprecate receivertest.NewNopFactoryForType (#11993) - `extension/experimental`: Deprecate extension/experimental in favor of extension/xextension (#12010) - `scraperhelper`: Move scraperhelper under scraper and in a separate module. (#11003) ### 💡 Enhancements 💡 - `scrapertest`: Add scrapertest package in a separate module (#11988) - `pdata`: Upgrade pdata to opentelemetry-proto v1.5.0 (#11932) ## v1.22.0/v0.116.0 ### 🛑 Breaking changes 🛑 - `component`: Remove deprecated TelemetrySettings.LeveledMeterProvider (#11811) - `scraperhelper`: Remove deprecated scraperhelper.Scraper and helpers (#11803) ### 🚩 Deprecations 🚩 - `connector`: Deprecate connectorprofiles module in favor of xconnector to allow adding more experimental data types. (#11778) - `consumererror`: Deprecate consumererrorprofiles module in favor of xconsumererror to allow adding more experimental data types. (#11778) - `consumer`: Deprecate consumerprofiles module in favor of xconsumer to allow adding more experimental data types. (#11778) - `exporterhelper`: Deprecate exporterhelperprofiles module in favor of xexporterhelper to allow adding more experimental data types. (#11778) - `exporter`: Deprecate exporterprofiles module in favor of xexporter to allow adding more experimental data types. (#11778) - `pipeline`: Deprecate pipelineprofiles module in favor of xpipeline to allow adding more experimental data types. (#11778) - `processorhelper`: Deprecate processorhelperprofiles module in favor of xprocessorhelper to allow adding more experimental data types. (#11778) - `processor`: Deprecate processorprofiles module in favor of xprocessor to allow adding more experimental data types. (#11778) - `receiver`: Deprecate receiverprofiles module in favor of xreceiver to allow adding more experimental data types. (#11778) - `receiver/scrapererror`: Remove the receiver/scrapererror alias. (#11003) ### 💡 Enhancements 💡 - `receiver/scraperhelper`: Add scraper for logs (#11238) ## v1.21.0/v0.115.0 ### 🛑 Breaking changes 🛑 - `extension/auth/authtest`: `authtest` is now its own module (#11465, #11705) - `pdata/pprofile`: AttributeTable is now a slice rather than a map (#11706) - `scraperhelper`: Remove deprecated scraperhelper funcs Scraper.ID, NewScraper, AddScraper. (#11710) - `mdatagen`: Remove deprecated LeveledMeter from the generated code (#11696) ### 🚩 Deprecations 🚩 - `component`: Mark `TelemetrySettings.LeveledMeterProvider` as deprecated (#11697) - `receiver/scraper`: Move receiver/scrapererror package to scraper/scrapererror and deprecate original receiver/scrapererror package. (#11003) - `scraperhelper`: Make Scraper compatible with the new scraper.Metrics (#11682) Deprecate scraperhelper.Scraper in favor of scraper.Metrics ## v1.20.0/v0.114.0 ### 🛑 Breaking changes 🛑 - `extensiontest`: Make extensiontest into its own module (#11463) - `component`: Make componenttest into its own module (#11464) - `expandconverter`: Remove deprecated expandvar converter (#11672) - `exporter`: Remove deprecated funcs Create[*]Exporter and [*]ExporterStability (#11662) - `exporterhelper`: Remove deprecated NewLogs[Request]Exporter funcs (#11661) - `extension`: Remove deprecated funcs CreateExtension and ExtensionStability (#11663) - `processortest`: Remove deprecated func NewUnhealthyProcessorCreateSettings (#11665) ### 🚩 Deprecations 🚩 - `component`: Deprecate `TelemetrySettings.LeveledMeterProvider` and undo deprecation of `TelemetrySettings.MeterProvider` (#11061) - `scraperhelper`: Deprecate Scraper.ID func, pass type when register Scraper (#11238) ## v1.19.0/v0.113.0 ### 🛑 Breaking changes 🛑 - `builder`: Remove deprecated flags from Builder (#11576) Here is the list of flags | --name, --description, --version, --otelcol-version, --go, --module ### 🚀 New components 🚀 - `processorhelperprofiles`: Add processorhelperprofiles to support profiles signal (#11556) ### 💡 Enhancements 💡 - `mdatagen`: Add newTelemetrySettings to be generated all the time even for pkg class (#11535) - `debugexporter`: Add profiles support to debug exporter (#11155) - `component`: Add UnmarshalText for StabilityLevel (#11520) ## v1.18.0/v0.112.0 ### 🛑 Breaking changes 🛑 - `service`: Change Host to not implement GetExportersWithSignal (#11444) Use Host.GetExporters if still needed. - `componentstatus`: Remove deprecated `NewInstanceIDWithPipelineIDs`, `AllPipelineIDsWithPipelineIDs`, and `WithPipelineIDs`. Use `NewInstanceID`, `AllPipelineIDs` and `WithPipelines` instead. (#11363) - `configgrpc`: Removed deprecated `ClientConfig.ToClientConnWithOptions`/`ServerConfig.ToServerWithOptions`. (#11359, #9480) These methods were renamed to `ClientConfig.ToClientConn`/`ServerConfig.ToServer` in v0.111.0. - `connector`: Put connectortest in its own module (#11216) - `exporter`: Disables setting batch option to batch sender directly. (#10368) Removed WithRequestBatchFuncs(BatcherOption) in favor of WithBatchFuncs(Option), where | BatcherOption is a function that operates on batch sender and Option is one that operates | on BaseExporter - `exporter`: Made mergeFunc and mergeSplitFunc required method of exporter.Request (#10368) mergeFunc and mergeSplitFunc used to be part of the configuration pass to the exporter. Now it is changed | to be a method function of request. - `componentprofiles`: Move componentprofiles to pipelineprofiles (#11421) - `processor`: Put processortest in its own module (#11218) - `receivertest`: Removed deprecated `NewNopFactoryForTypeWithSignal`. Use `NewNopFactoryForType` instead. (#11362) - `processor`: Remove deprecated funcs from processor package (#11368) - `receiver`: Remove deprecated funcs from receiver package (#11367) - `processorhelper`: Remove deprecated funcs/types from processorhelper & componenttest (#11302) - `service`: Remove deprecated `pipelines.ConfigWithPipelineID` and `Config.PipelinesWithPipelineID`. Use `pipelines.Config` and `Config.Pipelines` instead. (#11361) ### 🚩 Deprecations 🚩 - `extension`: Deprecate funcs that repeat extension in name (#11413) Factory.CreateExtension -> Factory.Create | Factory.ExtensionStability -> Factory.Stability - `exporter`: Deprecate funcs that repeat exporter in name (#11370) Factory.Create[Traces|Metrics|Logs|Profiles]Exporter -> Factory.Create[Traces|Metrics|Logs|Profiles] | Factory.[Traces|Metrics|Logs|Profiles]ExporterStability -> Factory.[Traces|Metrics|Logs|Profiles]Stability ### 🚀 New components 🚀 - `consumererrorprofiles`: Add new module consumererrorprofiles for consumer error profiles. (#11131) ### 💡 Enhancements 💡 - `configcompression`: Add support for lz4 compression (#9128) - `otlpexporter`: Add profiles support to OTLP exporter (#11435) - `otlphttpexporter`: Add profiles support to OTLP HTTP exporter (#11450) ## v1.17.0/v0.111.0 ### 🛑 Breaking changes 🛑 - `service/telemetry`: Change default metrics address to "localhost:8888" instead of ":8888" (#11251) This behavior can be disabled by disabling the feature gate 'telemetry.UseLocalHostAsDefaultMetricsAddress'. - `componentprofiles`: Removed deprecated `DataTypeProfiles`. Use `SignalProfiles` instead. (#11312) - `configgrpc`: Replace ToClientConn and ToServer with ToClientConnWithOptions and ToServerWithOptions. (#11271, #9480) `ClientConfig.ToClientConn` and `ServerConfig.ToServer` were deprecated in v0.110.0 in favor of `ClientConfig.ToClientConnWithOptions` and `ServerConfig.ToServerWithOptions` which use a more flexible option type. The original functions are now removed, and the new ones are renamed to the old names. The `WithOptions` names are kept as deprecated aliases for now. - `exporterhelper`: Removed deprecated `QueueTimeout`/`TimeoutSettings` aliases in favor of `QueueConfig`/`TimeoutConfig`. (#11264, #6767) `NewDefaultQueueSettings` and `NewDefaultTimeoutSettings` have been similarly renamed. - `exporterqueue`: Remove deprecated `Settings.DataType`. Use `Settings.Signal` instead. (#11305) - `exportertest`: Remove deprecated `CheckConsumeContractParams.DataType`. Use `CheckConsumeContractParams.Signal` instead. (#11305) - `component`: Removed deprecated `ErrDataTypeIsNotSupported`, `DataType`, `DataTypeTraces`, `DataTypeMetrics`, and `DataTypeLogs`. Use `pipeline.ErrSignalNotSupported`, `pipeline.Signal`, `pipeline.SignalTraces`, `pipeline.SignalMetrics`, and `pipeline.SignalLogs` instead. (#11253) - `pdata/pprofile`: Replace slices of values to slices of pointers for the `Mapping`, `Location`, `Line`, `Function`, `AttributeUnit`, `Link`, `Value`, `Sample` and `Labels` attributes. (#11339) - `receivertest`: Remove deprecated `CheckConsumeContractParams.DataType`. Use `CheckConsumeContractParams.Signal` instead. (#11304) - `scraperhelper`: Remove deprecated function `NewScraperWithComponentType`. (#11294) - `processorhelper`: Remove deprecated funcs form processorhelper.ObsReport (#11289) The "otelcol_processor_dropped_log_records", "otelcol_processor_dropped_log_records" | and "otelcol_processor_dropped_spans" metrics are complete removed, before they were always record with 0 values. ### 🚩 Deprecations 🚩 - `componentstatus`: Deprecated `NewInstanceIDWithPipelineIDs`, `AllPipelineIDsWithPipelineIDs`, and `WithPipelineIDs`. Use `NewInstanceID`, `AllPipelineIDs`, and `WithPipelines` instead. (#11313) - `processorhelper`: Deprecate unused and empty struct processorhelper.ObsReport (#11293) - `processor`: Deprecate funcs that repeat "processor" in name (#11310) Factory.Create[Traces|Metrics|Logs|Profiles]Processor -> Factory.Create[Traces|Metrics|Logs|Profiles] Factory.[Traces|Metrics|Logs|Profiles]ProcessorStability -> Factory.[Traces|Metrics|Logs|Profiles]Stability - `receiver`: Deprecate funcs that repeat "receiver" in name (#11287) Factory.Create[Traces|Metrics|Logs|Profiles]Receiver -> Factory.Create[Traces|Metrics|Logs|Profiles] Factory.[Traces|Metrics|Logs|Profiles]ReceiverStability -> Factory.[Traces|Metrics|Logs|Profiles]Stability - `receivertest`: Deprecated `NewNopFactoryForTypeWithSignal`. Use `NewNopFactoryForType` instead. (#11304) - `service`: Deprecates `Config.PipelinesWithPipelineID`, `pipelines.ConfigWithPipelineID` and `GetExportersWithSignal` interface implementation. Use `Config.Pipelines`, `pipelines.Config`, and `GetExporters` interface implementation instead. (#11303) ## v1.16.0/v0.110.0 ### 🛑 Breaking changes 🛑 - `otlpexporter`: The `TimeoutSettings` field in `otlpexporter.Config` was renamed to `TimeoutConfig`. (#11132) - `connector`: Change `TracesRouterAndConsumer`, `NewTracesRouter`, `MetricsRouterAndConsumer`, `NewMetricsRouter`, `LogsRouterAndConsumer`, and `NewLogsRouter` to use `pipeline.ID` instead of `component.ID`. (#11204) - `extension`: Remove deprecated extension interfaces. (#11043) They are now available in the `extensioncapabilities` module. ### 🚩 Deprecations 🚩 - `exporterhelper`: Deprecate TimeoutSettings/QueueSettings in favor of TimeoutConfig/QueueConfig. (#6767) - `configgrpc`: Deprecate `ClientConfig.ToClientConn`/`ServerConfig.ToServer` in favor of `ToClientConnWithOptions`/`ToServerWithOptions` (#9480) Users providing a grpc.DialOption/grpc.ServerOption should now wrap them into a generic option with `WithGrpcDialOption`/`WithGrpcServerOption`. - `componentprofiles`: Deprecates `DataTypeProfiles`. Use `SignalProfiles` instead. (#11204) - `componentstatus`: Deprecates `NewInstanceID`, `AllPipelineIDs`, and `WithPipelines`. Use `NewInstanceIDWithPipelineIDs`, `AllPipelineIDsWithPipelineIDs`, and `WithPipelineIDs` instead. (#11204) - `exporterqueue`: Deprecates `Settings.DataType`. Use `Settings.Signal` instead. (#11204) - `service`: Deprecates `pipelines.Config`. Use `pipelines.ConfigWithPipelineID` instead. (#11204) - `component`: Deprecates `DataType`, `DataTypeTraces`, `DataTypeMetrics`, and `DataTypeLogs`. Use `pipeline.Signal`, `SignalTraces`, `SignalMetrics`, and `SignalLogs` instead. (#11204) - `service`: Deprecates service's implementation of `GetExporters` interface. Use `GetExportersWithSignal` instead. (#11249) - `scraperhelper`: Deprecate NewScraperWithComponentType, should use NewScraper (#11159) ### 🚀 New components 🚀 - `pipeline`: Adds new `pipeline` module to house the concept of pipeline ID and Signal. (#11209) ### 💡 Enhancements 💡 - `pdata`: Add support to MoveTo for Map, allow avoiding copies (#11175) - `options`: Avoid using private types in public APIs and also protect options to be implemented outside this module. (#11054) - `mdatagen`: Avoid using private types in public APIs and also protect options to be implemented outside this module. (#11040) - `consumertest`: Introduce SampleCount method in ProfilesSink struct. (#11225) - `otlpreceiver`: Support profiles in the OTLP receiver (#11071) ## v1.15.0/v0.109.0 ### 🛑 Breaking changes 🛑 - `Remove `extensiontest` StatusWatcher helpers`: They were unused. They may be added back on a different module or after `componentstatus` is marked 1.0 (#11044) - `pprofile`: Change Profile ID field from a byte array to a custom data type (#11048) - `connector`: Remove deprecated connector builder (#11019) - `exporter`: Remove deprecated exporter builder (#11019) - `extension`: Remove deprecated extension builder (#11019) - `processor`: Remove deprecated processor builder (#11019) - `receiver`: Remove deprecated receiver builder (#11019) ### 🚩 Deprecations 🚩 - `configtelemetry`: Deprecating `TelemetrySettings.MeterProvider` in favour of `TelemetrySettings.LeveledMeterProvider` (#10912) - `extension`: Deprecate `extension.ConfigWatcher`, `extension.PipelineWatcher` and `extension.Dependent` in favor of equivalents in the `extensioncapabilities` module. (#11000) - `scraperhelper`: deprecate NewScraper, should use NewScraperWithComponentType (#11082) ### 🚀 New components 🚀 - `extensioncapabilities`: Create a new module for optional extension capabilities. (#11000) ### 💡 Enhancements 💡 - `connectorprofiles`: Add ProfilesRouterAndConsumer interface, and NewProfilesRouter method. (#11023) - `pprofileotlp`: Introduce grpc service implementation of pprofileotlp (#11048) - `pprofile`: Introduce marshalling and unmarshalling of pprofile data (#11048) - `service`: Support profiles in the service package (#11024) ## v1.14.1/v0.108.1 ## v1.14.0/v0.108.0 ### 🛑 Breaking changes 🛑 - `extensions`: Remove `StatusWatcher` interface. Use `componentstatus.Watcher` instead. (#10777) - `component`: Removed Status related types and functions. Use `componentstatus` instead. (#10777) - `component`: Remove `ReportStatus` from `TelemetrySettings`. Use `componentstatus.ReportStatus` instead. (#10777) - `componentstatus`: Make componentstatus.InstanceID immutable. (#10494) ### 🚩 Deprecations 🚩 - `scraperhelper`: deprecate NewObsReport, ObsReport, ObsReportSettings, scrapers should use NewScraperControllerReceiver (#10959) - `mdatagen`: Deprecating generated `Meter` func in favour of `LeveledMeter` (#10939) - `connector`: Deprecate connector.Builder, and move it into an internal package of the service module (#10784) - `exporter`: Deprecate exporter.Builder, and move it into an internal package of the service module (#10783) - `extension`: Deprecate extension.Builder, and move it into an internal package of the service module (#10785) - `processor`: Deprecate processor.Builder, and move it into an internal package of the service module (#10782) - `receiver`: Deprecate receiver.Builder, and move it into an internal package of the service module (#10781) ## v1.13.0/v0.107.0 ### 🛑 Breaking changes 🛑 - `otelcol`: Delete deprecated NewCommandMustSetProvider (#10778) - `component`: Removes the deprecated `Host.GetFactory` method. (#10771) - `otelcoltest`: The `otelcol.LoadConfig` method no longer sets the `expandconverter`. (#10510) - `ocb`: Collectors built with OCB will no longer include the `expandconverter` (#10510) - `exporterhelper`: Delete deprecated `exporterhelper.ObsReport` and `exporterhelper.NewObsReport` (#10779, #10592) ### 🚩 Deprecations 🚩 - `expandconverter`: Deprecate `expandconverter`. (#10510) ### 🚀 New components 🚀 - `componentstatus`: Adds new componentstatus module that will soon replace status content in component. (#10730) - `connector/connectorprofiles`: Allow handling profiles in connector. (#10703) - `exporter/exporterprofiles`: Allow handling profiles in exporter. (#10702) - `processor/processorprofiles`: Allow handling profiles in processor. (#10691) - `receiver/receiverprofiles`: Allow handling profiles in receiver. (#10690) ### 💡 Enhancements 💡 - `confmap`: Check that providers have a correct scheme when building a confmap.Resolver. (#10786) - `confighttp`: Add `NewDefaultCORSConfig` function to initialize the default `confighttp.CORSConfig` (#9655) ## v0.106.0 ### 🛑 Breaking changes 🛑 - `configauth`: removing deprecated methods GetServerAuthenticatorContext and GetClientAuthenticatorContext (#9808) - `connector,exporter,receiver,extension,processor`: Remove deprecated funcs/structs (#10423) Remove the following funcs & structs: - connector.CreateSettings -> connector.Settings - connectortest.NewNopCreateSettings -> connectortest.NewNopSettings - exporter.CreateSettings -> exporter.Settings - exportertest.NewNopCreateSettings -> exportertest.NewNopSettings - extension.CreateSettings -> extension.Settings - extensiontest.NewNopCreateSettings -> extensiontest.NewNopSettings - processor.CreateSettings -> processor.Settings - processortest.NewNopCreateSettings -> processortest.NewNopSettings - receiver.CreateSettings -> receiver.Settings - receivertest.NewNopCreateSettings -> receivertest.NewNopSettings - `component/componenttest`: Add optional ...attribute.KeyValue argument to TestTelemetry.CheckExporterMetricGauge. (#10593) - `confighttp`: Auth data type signature has changed (#4806) As part of the linked PR, the `auth` attribute was moved from `configauth.Authentication` to a new `AuthConfig`, which contains a `configauth.Authentication`. For end-users, this is a non-breaking change. For users of the API, create a new AuthConfig using the `configauth.Authentication` instance that was being used before. - `mdatagen`: Remove WithAttributes option from the telemetry builder constructor. (#10608) Attribute sets for async instruments now can be set as options to callback setters and async instruments initializers. This allows each async instrument to have its own attribute set. - `service/extensions`: Adds `Options` to `extensions.New`. (#10728) This is only a breaking change if you are depending on `extensions.New`'s signature. Calls to `extensions.New` are not broken. ### 🚩 Deprecations 🚩 - `component`: Deprecates Host.GetFactory. (#10709) ### 🚀 New components 🚀 - `component/componentprofiles`: Add componentprofiles module. (#10525) ### 💡 Enhancements 💡 - `exporter, processor, receiver`: Document factory functions. (#9323) - `component`: Document status enums and New constructors (#9822) - `confighttp, configgrpc`: Remove the experimental comment on `IncludeMetadata` in confighttp and configgrpc (#9381) - `confighttp`: Add `confighttp.NewDefaultServerConfig()` to instantiate the default HTTP server configuration (#9655) - `consumer/consumertest`: Allow testing profiles with consumertest. (#10692) ### 🧰 Bug fixes 🧰 - `confmap`: Fix wrong expansion of environment variables escaped with `$$`, e.g. `$${ENV_VAR}` and `$$ENV_VAR`. (#10713) This change fixes the issue where environment variables escaped with $$ were expanded. The collector now converts `$${ENV_VAR}` to `${ENV_VAR}` and `$$ENV_VAR` to `$ENV_VAR` without further expansion. ## v1.12.0/v0.105.0 ### 🛑 Breaking changes 🛑 - `otelcol`: Obtain the Collector's effective config from otelcol.Config (#10139) `otelcol.Collector` will now marshal `confmap.Conf` objects from `otelcol.Config` itself. - `otelcoltest`: Remove deprecated methods `LoadConfigWithSettings` and `LoadConfigAndValidateWithSettings` (#10512) ### 🚩 Deprecations 🚩 - `configauth`: Deprecated `Authentication.GetClientAuthenticatorContext` and `Authentication.GetServerAuthenticatorContext` (#10578) - `otelcol`: Deprecate `otelcol.ConfmapProvider` (#10139) `otelcol.Collector` will now marshal `confmap.Conf` objects from `otelcol.Config` itself. - `otelcol`: Deprecate `(*otelcol.ConfigProvider).GetConfmap` (#10139) Call `(*confmap.Conf).Marshal(*otelcol.Config)` to get the Collector's configuration. - `exporterhelper`: Deprecate the obsreport API in the exporterhelper package. (#10592) ### 🚀 New components 🚀 - `consumer/consumerprofiles`: Allow handling profiles in consumer. (#10464) ## v1.11.0/v0.104.0 ### 🛑 Breaking changes 🛑 - `otelcol`: The `otelcol.NewCommand` now requires at least one provider be set. (#10436) - `component/componenttest`: Added additional "inserted" count to `TestTelemetry.CheckProcessor*` methods. (#10353) ### 🚩 Deprecations 🚩 - `otelcoltest`: Deprecates `LoadConfigWithSettings` and `LoadConfigAndValidateWithSettings`. Use `LoadConfig` and `LoadConfigAndValidate` instead. (#10417) - `otelcol`: The `otelcol.NewCommandMustSetProvider` is deprecated. Use `otelcol.NewCommand` instead. (#10436) ### 🚀 New components 🚀 - `otelcoltest`: Split off go.opentelemetry.io/collector/otelcol/otelcoltest into its own module (#10417) ### 💡 Enhancements 💡 - `pdata/pprofile`: Add pprofile wrapper to convert proto into pprofile. (#10401) - `pdata/testdata`: Add pdata testdata for profiles. (#10401) ## v1.10.0/v0.103.0 ### 🛑 Breaking changes 🛑 - `component`: Remove deprecated `component.UnmarshalConfig` (#7102) - `confighttp`: Use `confighttp.ServerConfig` as part of zpagesextension.Config. Previously the extension used `confignet.TCPAddrConfig` (#9368) ### 🚩 Deprecations 🚩 - `connector`: Deprecate CreateSettings and NewNopCreateSettings (#9428) The following methods are being renamed: - connector.CreateSettings -> connector.Settings - connector.NewNopCreateSettings -> connector.NewNopSettings - `exporter`: Deprecate CreateSettings and NewNopCreateSettings (#9428) The following methods are being renamed: - exporter.CreateSettings -> exporter.Settings - exporter.NewNopCreateSettings -> exporter.NewNopSettings - `extension`: Deprecate CreateSettings and NewNopCreateSettings (#9428) The following methods are being renamed: - extension.CreateSettings -> extension.Settings - extension.NewNopCreateSettings -> extension.NewNopSettings - `processor`: Deprecate CreateSettings and NewNopCreateSettings (#9428) The following methods are being renamed: - processor.CreateSettings -> processor.Settings - processor.NewNopCreateSettings -> processor.NewNopSettings - `receiver`: Deprecate CreateSettings and NewNopCreateSettings (#9428) The following methods are being renamed: - receiver.CreateSettings -> receiver.Settings - receiver.NewNopCreateSettings -> receiver.NewNopSettings - `configauth`: Deprecate `GetClientAuthenticator` and `GetServerAuthenticator`, use `GetClientAuthenticatorContext` and `GetServerAuthenticatorContext` instead. (#9808) - `confighttp`: Deprecate `ClientConfig.CustomRoundTripper` (#8627) Set the `Transport` field on the `*http.Client` object returned from `(ClientConfig).ToClient` instead. - `filter`: Deprecate the `filter.CombinedFilter` struct (#10348) - `otelcol`: Deprecate `otelcol.NewCommand`. Use `otelcol.NewCommandMustProviderSettings` instead. (#10359) - `otelcoltest`: Deprecate `LoadConfig` and `LoadConfigAndValidate`. Use `LoadConfigWithSettings` and `LoadConfigAndValidateWithSettings` instead (#10359) ### 💡 Enhancements 💡 - `confmap`: Adds `confmap.Retrieved.AsString` method that returns the configuration value as a string (#9532) - `confmap`: Adds `confmap.NewRetrievedFromYAML` helper to create `confmap.Retrieved` values from YAML bytes (#9532) ## v0.102.1 No API-only changes on this release. **This release addresses [GHSA-c74f-6mfw-mm4v](https://github.com/open-telemetry/opentelemetry-collector/security/advisories/GHSA-c74f-6mfw-mm4v) for `configgrpc`.** ## v1.9.0/v0.102.0 **This release addresses [GHSA-c74f-6mfw-mm4v](https://github.com/open-telemetry/opentelemetry-collector/security/advisories/GHSA-c74f-6mfw-mm4v) for `confighttp`.** ### 🛑 Breaking changes 🛑 - `otelcol`: Remove deprecated `ConfigProvider` field from `CollectorSettings` (#10281) - `exporterhelper`: remove deprecated RequestMarshaler & RequestUnmarshaler types (#10283) - `service`: remove deprecated Telemetry struct and New func (#10285) - `configtls`: remove deprecated LoadTLSConfigContext funcs (#10283) ### 🚩 Deprecations 🚩 - `component`: Deprecate `component.UnmarshalConfig`, use `(*confmap.Conf).Unmarshal(&intoCfg)` instead. (#7102) - `service/telemetry`: Deprecate telemetry.New in favor of telemetry.NewFactory (#4970) ### 💡 Enhancements 💡 - `confmap`: Allow setting a default Provider on a Resolver to use when `${}` syntax is used without a scheme (#10182) - `pdata`: Introduce string and int64 slices to pcommon (#10148) - `pdata`: Introduce generated experimental pdata for profiling signal. (#10195) - `confmap`: Remove top level condition when considering struct as Unmarshalers (#7101) ### 🧰 Bug fixes 🧰 - `otelcol`: Update validate command to use the new configuration options (#10203) ## v1.8.0/v0.101.0 ### 🛑 Breaking changes 🛑 - `confighttp`: Removes deprecated functions `ToClientContext`, `ToListenerContext`, and `ToServerContext`. (#10138) - `confmap`: Deprecate `NewWithSettings` for all Providers and `New` for all Converters (#10134) Use `NewFactory` instead for all affected modules. - `confmap`: Remove deprecated `Providers` and `Converters` from `confmap.ResolverSettings` (#10173) Use `ProviderSettings` and `ConverterSettings` instead. ### 🧰 Bug fixes 🧰 - `otelcol`: Add explicit mapstructure tags to main configuration struct (#10152) - `confmap`: Support string-like types as map keys when marshaling (#10137) ## v1.7.0/v0.100.0 ### 💡 Enhancements 💡 - `configgrpc`: Adds `NewDefault*` functions for all the config structs. (#9654) - `exporterqueue`: Expose ErrQueueIsFull so upstream components can retry or apply backpressure. (#10070) ### 🧰 Bug fixes 🧰 - `mdatagen`: Call connectors with routers to be the same as the service graph (#10079) ## v1.6.0/v0.99.0 ### 🛑 Breaking changes 🛑 - `component`: Removed deprecated function `GetExporters` from `component.Host` interface (#9987) ### 🚩 Deprecations 🚩 - `confighttp`: deprecate ToClientContext, ToServerContext, ToListenerContext, replaced by ToClient, ToServer, ToListener (#9807) - `configtls`: Deprecates `ClientConfig.LoadTLSConfigContext` and `ServerConfig.LoadTLSConfigContext`, use `ClientConfig.LoadTLSConfig` and `ServerConfig.LoadTLSConfig` instead. (#9945) - `confmap`: Deprecate the `Providers` and `Converters` fields in `confmap.ResolverSettings` (#9516) Use the `ProviderFactories` and `ConverterFactories` fields instead. ### 💡 Enhancements 💡 - `configauth`: Adds `NewDefault*` functions for all the config structs. (#9821) - `configtls`: Adds `NewDefault*` functions for all the config structs. (#9658) - `pmetric`: Support metric.metadata in pdata/pmetric (#10006) ## v1.5.0/v0.98.0 ### 🛑 Breaking changes 🛑 - `component`: Restricts maximum length for `component.Type` to 63 characters. (#9872) - `configgrpc`: Remove deprecated `ToServerContext`, use `ToServer` instead. (#9836) - `configgrpc`: Remove deprecated `SanitizedEndpoint`. (#9836) - `configtls`: Remove Deprecated `TLSSetting`, `TLSClientSetting`, and `TLSServerSetting`. (#9786) - `configtls`: Rename `TLSSetting` to `Config` on `ClientConfig` and `ServerConfig`. (#9786) ### 🚩 Deprecations 🚩 - `confighttp`: Deprecate `ToClient`,`ToListener`and `ToServer` use `ToClientContext`,`ToListenerContext` and `ToServerContext`instead. (#9807) - `configtls`: Deprecate `ClientConfig.LoadTLSConfig` and `ServerConfig.LoadTLSConfig`, use `ClientConfig.LoadTLSConfigContext` and `ServerConfig.LoadTLSConfigContext` instead. (#9811) ### 💡 Enhancements 💡 - Introduce new module for generating pdata: pdata/testdata (#9886) - `exporterhelper`: Make the `WithBatcher` option available for regular exporter helpers based on OTLP data type. (#8122) Now, `WithBatcher` can be used with both regular exporter helper (e.g. NewTracesExporter) and the request-based exporter helper (e.g. NewTracesRequestExporter). The request-based exporter helpers require `WithRequestBatchFuncs` option providing batching functions. - `confmap`: Creates a logger in the confmap.ProviderSettings and uses it to log when there is a missing or blank environment variable referenced in config. For now the noop logger is used everywhere except tests. (#5615) ## v1.4.0/v0.97.0 ### 🛑 Breaking changes 🛑 - `configgrpc`: Remove deprecated `ToServer` function. (#9787) - `confignet`: Change `Transport` field from `string` to `TransportType` (#9385) - `component`: Change underlying type of `component.Type` to an opaque struct. (#9208) - `obsreport`: Remove deprecated obsreport/obsreporttest package. (#9724) - `component`: Remove deprecated error `ErrNilNextConsumer` (#9322) - `connector`: Remove `LogsRouter`, `MetricsRouter` and `TracesRouter`. Use `LogsRouterAndConsumer`, `MetricsRouterAndConsumer`, `TracesRouterAndConsumer` respectively instead. (#9095) - `receiver`: Remove deprecated struct `ScraperControllerSettings` and function `NewDefaultScraperControllerSettings` (#6767) - `confmap`: Remove deprecated `provider.New` methods, use `NewWithSettings` moving forward. (#9443) ### 🚩 Deprecations 🚩 - `configgrpc`: Deprecated `ToServerContext`, use `ToServer` instead. (#9787) - `configgrpc`: Deprecate `SanitizedEndpoint` (#9788) ### 💡 Enhancements 💡 - `exporterhelper`: Add experimental batching capabilities to the exporter helper (#8122) - `confignet`: Adds `NewDefault*` functions for all the config structs. (#9656) - `configtls`: Validates TLS min_version and max_version (#9475) Introduces `Validate()` method in TLSSetting. - `exporterhelper`: Invalid exporterhelper options now make the exporter creation error out instead of panicking. (#9717) - `components`: Give NoOp components a unique name (#9637) ## v1.3.0/v0.96.0 ### 🚩 Deprecations 🚩 - `configgrpc`: Deprecates `ToServer`. Use `ToServerContext` instead. (#9624) - `component`: deprecate component.ErrNilNextConsumer (#9526) - `configtls`: Rename TLSClientSetting, TLSServerSetting, and TLSSetting based on the naming convention used in other config packages. (#9474) ### 💡 Enhancements 💡 - `receivertest`: add support for metrics in contract checker (#9551) ## v1.2.0/v0.95.0 ### 🛑 Breaking changes 🛑 - `all`: Bump minimum go version to go 1.21 (#9507) - `service/telemetry`: Delete generated_config types, use go.opentelemetry.io/contrib/config types instead (#9546) - `configcompression`: Remove deprecated `configcompression` types, constants and methods. (#9388) - `component`: Remove `host.ReportFatalError` (#6344) - `configgrpc`: Remove deprecated `configgrpc.ServerConfig.ToListener` (#9481) - `confmap`: Remove deprecated `confmap.WithErrorUnused` (#9484) ### 🚩 Deprecations 🚩 - `confignet`: Deprecate `confignet.NetAddr` and `confignet.TCPAddr` in favor of `confignet.AddrConfig` and `confignet.TCPAddrConfig`. (#9509) - `config/configgrpc`: Deprecate `configgrpc.ClientConfig.SanitizedEndpoint`, `configgrpc.ServerConfig.ToListener` and `configgrpc.ServerConfig.ToListenerContext` (#9481, #9482) - `scraperhelper`: Deprecate ScraperControllerSettings, use ControllerConfig instead (#6767) ## v1.1.0/v0.94.0 ### 🛑 Breaking changes 🛑 - `confignet`: Remove deprecated `DialContext` and `ListenContext` functions (#9363) - `confmap/converter/expandconverter`: Add `confmap.ConverterSettings` argument to experimental `expandconverter.New` function. (#5615, #9162) - The `confmap.ConverterSettings` struct currently has no fields. It will be used to pass a logger. - `component`: Remove deprecated funcs and types (#9283) - `otlpexporter`: Config struct is moving from embedding the deprecated GRPCClientSettings struct to using ClientConfig instead. (#6767) - `otlphttpexporter`: otlphttpexporter.Config embeds the struct confighttp.ClientConfig instead of confighttp.HTTPClientSettings (#6767) - `otlpreceiver`: HTTPConfig struct is moving from embedding the deprecated ServerSettings struct to using HTTPServerConfig instead. (#6767) - `component`: Validate component.Type at creation and unmarshaling time. (#9208) - A component.Type must start with an ASCII alphabetic character and can only contain ASCII alphanumeric characters and '_'. ### 🚩 Deprecations 🚩 - `configcompressions`: Deprecate `IsCompressed`. Use `CompressionType.IsCompressed instead` instead. (#9435) - `configcompression`: Deprecate `CompressionType`, use `Type` instead. (#9416) - `confighttp`: Deprecate CORSSettings, use CORSConfig instead (#6767) - `configgrpc`: Deprecate `ToListener` function in favor of `ToListenerContext` (#9389) - `configgrpc`: Deprecate GRPCServerSettings, use ServerConfig instead (#6767) - `confighttp`: Deprecate HTTPClientSettings, use ClientConfig instead (#6767) - `confighttp`: Deprecate HTTPServerSettings, use ServerConfig instead (#6767) - `confmap/provider`: Deprecate .New in favor of .NewWithSettings for all core providers (#5615, #9162) - NewWithSettings now takes an empty confmap.ProviderSettings struct. This will be used to pass a logger in the future. ### 💡 Enhancements 💡 - `exporter/exporterhelper`: Add API for enabling queue in the new exporter helpers. (#7874) The following experimental API is introduced in exporter package: - `exporterhelper.WithRequestQueue`: a new exporter helper option for using a queue. - `exporterqueue.Queue`: an interface for queue implementations. - `exporterqueue.Factory`: a queue factory interface, implementations of this interface are intended to be used with WithRequestQueue option. - `exporterqueue.Settings`: queue factory settings. - `exporterqueue.Config`: common configuration for queue implementations. - `exporterqueue.NewDefaultConfig`: a function for creating a default queue configuration. - `exporterqueue.NewMemoryQueueFactory`: a new factory for creating a memory queue. - `exporterqueue.NewPersistentQueueFactory: a factory for creating a persistent queue. - `featuregate`: Add the `featuregate.ErrAlreadyRegistered` error, which is returned by `featuregate.Registry`'s `Register` when adding a feature gate that is already registered. (#8622) Use `errors.Is` to check for this error. ## v0.93.0 ### 🛑 Breaking changes 🛑 - `bug_fix`: Implement `encoding.BinaryMarshaler` interface to prevent `configopaque` -> `[]byte` -> `string` conversions from leaking the value (#9279) - `configopaque`: configopaque.String implements `fmt.Stringer` and `fmt.GoStringer`, outputting [REDACTED] when formatted with the %s, %q or %#v verbs` (#9213) This may break applications that rely on the previous behavior of opaque strings with `fmt.Sprintf` to e.g. build URLs or headers. Explicitly cast the opaque string to a string before using it in `fmt.Sprintf` to restore the previous behavior. - `all`: Remove obsolete "// +build" directives (#9304) - `connectortest`: Remove deprecated connectortest router helpers. (#9278) ### 🚩 Deprecations 🚩 - `obsreporttest`: deprecate test funcs/structs (#8492) The following methods/structs have been moved from obsreporttest to componenttest: - obsreporttest.TestTelemetry -> componenttest.TestTelemetry - obsreporttest.SetupTelemetry -> componenttest.SetupTelemetry - obsreporttest.CheckScraperMetrics -> TestTelemetry.CheckScraperMetrics - obsreporttest.TestTelemetry.TelemetrySettings -> componenttest.TestTelemetry.TelemetrySettings() - `confignet`: Deprecates `DialContext` and `ListenContext` functions. Use `Dial` and `Listen` instead. (#9258) Unlike the previous `Dial` and `Listen` functions, the new `Dial` and `Listen` functions take a `context.Context` like `DialContext` and `ListenContext`. ## v1.0.1/v0.92.0 ### 🛑 Breaking changes 🛑 - `otlpexporter`: Change Config members names to use Config suffix. (#9091) - `component`: Remove deprecated unused TelemetrySettingsBase (#9145) ### 🚩 Deprecations 🚩 - `confignet`: Deprecates the `Dial` and `Listen` functions in favor of `DialContext` and `ListenContext`. (#9163) - `component`: Deprecate unnecessary type StatusFunc (#9146) ## v0.91.0 ## v1.0.0/v0.90.0 ### 🛑 Breaking changes 🛑 - `exporterhelper`: Replace converter interface with function in the new experimental exporter helper. (#8122) - `featuregate`: Remove deprecate function `featuregate.NewFlag` (#8727) Use `featuregate.Registry`'s `RegisterFlags` method instead. ### 🚩 Deprecations 🚩 - `telemetry`: deprecate jsonschema generated types (#15009) ### 💡 Enhancements 💡 - `pdata`: Add ZeroThreshold field to exponentialHistogramDataPoint in pmetric package. (#8802) ## v1.0.0-rcv0018/v0.89.0 ### 🛑 Breaking changes 🛑 - `otelcol`: CollectorSettings.Factories now expects: `func() (Factories, error)` (#8478) - `exporter/exporterhelper`: The experimental Request API is updated. (#7874) - `Request` interface now includes ItemsCount() method. - `RequestItemsCounter` is removed. - The following interfaces are added: - Added an optional interface for handling errors that occur during request processing `RequestErrorHandler`. - Added a function to unmarshal bytes into a Request `RequestUnmarshaler`. - Added a function to marshal a Request into bytes `RequestMarshaler` ### 🚩 Deprecations 🚩 - `featuregate`: Deprecate `featuregate.NewFlag` in favor of `featuregate.Registry`'s `RegisterFlags` method (#8727) ### 💡 Enhancements 💡 - `featuregate`: Add validation for feature gates ID, URL and versions. (#8766) Feature gates IDs are now explicitly restricted to ASCII alphanumerics and dots. ## v1.0.0-rcv0017/v0.88.0 ### 💡 Enhancements 💡 - `pdata`: Add IsReadOnly() method to p[metrics|logs|traces].[Metrics|Logs|Spans] pdata structs allowing to check if the struct is read-only. (#6794) ## v1.0.0-rcv0016/v0.87.0 ### 💡 Enhancements 💡 - `pdata`: Introduce API to control pdata mutability (#6794) This change introduces new API pdata methods to control the mutability: - p[metric|trace|log].[Metrics|Traces|Logs].MarkReadOnly() - marks the pdata as read-only. Any subsequent mutations will result in a panic. - p[metric|trace|log].[Metrics|Traces|Logs].IsReadOnly() - returns true if the pdata is marked as read-only. Currently, all the data is kept mutable. This API will be used by fanout consumer in the following releases. ### 🛑 Breaking changes 🛑 - `obsreport`: remove methods/structs deprecated in previous release. (#8492) - `extension`: remove deprecated Configs and Factories (#8631) ## v1.0.0-rcv0015/v0.86.0 ### 🛑 Breaking changes 🛑 - `service`: remove deprecated service.PipelineConfig (#8485) ### 🚩 Deprecations 🚩 - `obsreporttest`: deprecate To*CreateSettings funcs in obsreporttest (#8492) The following TestTelemetry methods have been deprecated. Use structs instead: - ToExporterCreateSettings -> exporter.CreateSettings - ToProcessorCreateSettings -> processor.CreateSettings - ToReceiverCreateSettings -> receiver.CreateSettings - `obsreport`: Deprecating `obsreport.Exporter`, `obsreport.ExporterSettings`, `obsreport.NewExporter` (#8492) These deprecated methods/structs have been moved to exporterhelper: - `obsreport.Exporter` -> `exporterhelper.ObsReport` - `obsreport.ExporterSettings` -> `exporterhelper.ObsReportSettings` - `obsreport.NewExporter` -> `exporterhelper.NewObsReport` - `obsreport`: Deprecating `obsreport.BuildProcessorCustomMetricName`, `obsreport.Processor`, `obsreport.ProcessorSettings`, `obsreport.NewProcessor` (#8492) These deprecated methods/structs have been moved to processorhelper: - `obsreport.BuildProcessorCustomMetricName` -> `processorhelper.BuildCustomMetricName` - `obsreport.Processor` -> `processorhelper.ObsReport` - `obsreport.ProcessorSettings` -> `processorhelper.ObsReportSettings` - `obsreport.NewProcessor` -> `processorhelper.NewObsReport` - `obsreport`: Deprecating obsreport scraper and receiver API (#8492) These deprecated methods/structs have been moved to receiverhelper and scraperhelper: - `obsreport.Receiver` -> `receiverhelper.ObsReport` - `obsreport.ReceiverSettings` -> `receiverhelper.ObsReportSettings` - `obsreport.NewReceiver` -> `receiverhelper.NewObsReport` - `obsreport.Scraper` -> `scraperhelper.ObsReport` - `obsreport.ScraperSettings` -> `scraperhelper.ObsReportSettings` - `obsreport.NewScraper` -> `scraperhelper.NewObsReport` ### 💡 Enhancements 💡 - `otelcol`: Splitting otelcol into its own module. (#7924) - `service`: Split service into its own module (#7923) ## v0.85.0 ## v0.84.0 ### 💡 Enhancements 💡 - `exporter/exporterhelper`: Introduce a new exporter helper that operates over client-provided requests instead of pdata (#7874) The following experimental API is introduced in exporter/exporterhelper package: - `NewLogsRequestExporter`: a new exporter helper for logs. - `NewMetricsRequestExporter`: a new exporter helper for metrics. - `NewTracesRequestExporter`: a new exporter helper for traces. - `Request`: an interface for client-defined requests. - `RequestItemsCounter`: an optional interface for counting the number of items in a Request. - `LogsConverter`: an interface for converting plog.Logs to Request. - `MetricsConverter`: an interface for converting pmetric.Metrics to Request. - `TracesConverter`: an interface for converting ptrace.Traces to Request. All the new APIs are intended to be used by exporters that need to operate over client-provided requests instead of pdata. - `otlpreceiver`: Export HTTPConfig as part of the API for creating the otlpreceiver configuration. (#8175) Changes signature of receiver/otlpreceiver/config.go type httpServerSettings to HTTPConfig. ## v0.83.0 ### 🛑 Breaking changes 🛑 - `all`: Remove go 1.19 support, bump minimum to go 1.20 and add testing for 1.21 (#8207) ### 💡 Enhancements 💡 - `changelog`: Generate separate changelogs for end users and package consumers (#8153) ================================================ FILE: CHANGELOG.md ================================================ # Changelog Starting with version v0.83.0, this changelog includes only user-facing changes. If you are looking for developer-facing changes, check out [CHANGELOG-API.md](./CHANGELOG-API.md). ## v1.54.0/v0.148.0 ### ❗ Known Issues ❗ - `service`: The collector's internal Prometheus metrics endpoint (`:8888`) now emits OTel service labels with underscore names (`service_name`, `service_instance_id`, `service_version`) instead of dot-notation names (`service.name`, `service.instance.id`, `service.version`). Users scraping this endpoint with the Prometheus receiver will see these renamed labels in resource and datapoint attributes. As a workaround, add the following `metric_relabel_configs` to your scrape config in prometheus receiver: ```yaml metric_relabel_configs: - source_labels: [service_name] target_label: service.name - source_labels: [service_instance_id] target_label: service.instance.id - source_labels: [service_version] target_label: service.version - regex: service_name|service_instance_id|service_version action: labeldrop ``` See https://github.com/open-telemetry/opentelemetry-collector/issues/14814 for details and updates. ### 🛑 Breaking changes 🛑 - `all`: Change metric units to be singular to match OTel specification, e.g. `{requests}` -> `{request}` (#14753) ### 💡 Enhancements 💡 - `cmd/mdatagen`: Add deprecated_type field to allow specifying an alias for component types. (#14718) - `cmd/mdatagen`: Generate entity-scoped MetricsBuilder API that enforces entity-metric associations at compile time (#14659) - `cmd/mdatagen`: Skip generating reaggregation config options for metrics that have no aggregatable attributes. (#14689) - `pkg/service`: The internal status reporter no longer drops repeated Ok and RecoverableError statuses (#14282) Status events can now carry metadata and there's value in allowing them to be emitted despite the status value itself not changing. ### 🧰 Bug fixes 🧰 - `cmd/builder`: Add `.exe` to output binary names when building for Windows targets. (#12591) - `exporter/debug`: Add printing of metric metadata in detailed verbosity. (#14667) - `exporter/otlp_grpc`: Prevent nil pointer panic when push methods are called before the OTLP exporter initializes its gRPC clients. (#14663) When the sending queue and retry are disabled, calling ConsumeTraces, ConsumeMetrics, ConsumeLogs, or ConsumeProfiles before the OTLP exporter initializes its gRPC clients could cause a nil pointer dereference panic. The push methods now return an error instead of panicking. - `exporter/otlp_http`: Show the actual destination URL in error messages when request URL is modified by middleware. (#14673) Unwraps the `*url.Error` returned by `http.Client.Do()` to prevent misleading error logs when a middleware extension dynamically updates the endpoint. - `pdata/pprofile`: Switch the dictionary of dictionary tables entries only once when merging profiles (#14709) For dictionary table data, we used to switch their dictionaries when doing the switch for the data that uses them. However, when an entry is associated with multiple other data (several samples can use the same stack), we would have been switching the dictionaries of the entry multiple times. We now switch dictionaries for dictionary table data only once, before switching the resource profiles. ## v1.53.0/v0.147.0 ### 💡 Enhancements 💡 - `exporter/debug`: Output bucket counts for exponential histogram data points in normal verbosity. (#10463) - `pkg/exporterhelper`: Add `metadata_keys` configuration to `sending_queue.batch.partition` to partition batches by client metadata (#14139) The `metadata_keys` configuration option is now available in the `sending_queue.batch.partition` section for all exporters. When specified, batches are partitioned based on the values of the listed metadata keys, allowing separate batching per metadata partition. This feature is automatically configured when using `exporterhelper.WithQueue()`. ### 🧰 Bug fixes 🧰 - `cmd/builder`: Fix duplicate error output when CLI command execution fails in the builder tool. (#14436) - `cmd/mdatagen`: Fix duplicate error output when CLI command execution fails in the mdatagen tool. (#14436) - `cmd/mdatagen`: Fix semconv URL validation for metrics with underscores in their names (#14583) Metrics like `system.disk.io_time` now correctly validate against semantic convention URLs containing underscores in the anchor tag. - `extension/memory_limiter`: Use ChainUnaryInterceptor instead of UnaryInterceptor to allow multiple interceptors. (#14634) If multiple extensions that use the UnaryInterceptor are set the binary panics at start time. - `extension/memory_limiter`: Add support for streaming services. (#14634) - `pkg/config/configmiddleware`: Add context.Context to HTTP middleware interface constructors. (#14523) This is a breaking API change for components that implement or use extensionmiddleware. - `pkg/confmap`: Fix another issue where configs could fail to decode when using interpolated values in string fields. (#14034) For example, a resource attribute can be set via an environment variable to a string that is parseable as a number, e.g. `1234`. (A similar bug was fixed in a previous release: that one was triggered when the field was nested in a struct, whereas this one is triggered when the field internally has type "pointer to string" rather than "string".) - `pkg/otelcol`: The featuregate subcommand now rejects extra positional arguments instead of silently ignoring them. (#14554) - `pkg/queuebatch`: Fix data race in partition_batcher where resetTimer() was called outside mutex, causing concurrent timer.Reset() calls and unpredictable batch flush timing under load. (#14491) - `pkg/scraperhelper`: Log scrapers now emit log-appropriate receiver telemetry (#14654) Log scrapers previously emitted the same receiver telemetry as metric scrapers, such as the otelcol_receiver_accepted_metric_points metric (instead of otelcol_receiver_accepted_log_records), or spans named receiver/myreceiver/MetricsReceived (instead of receiver/myreceiver/LogsReceived). This did not affect scraper-specific spans and metrics. - `processor/batch`: Fixes a bug where the batch processor would not copy `SchemaUrl` metadata from resource and scope containers during partial batch splits. (#12279, #14620) ## v1.52.0/v0.146.1 ## v0.146.0 ### 🛑 Breaking changes 🛑 - `all`: Increase minimum Go version to 1.25 (#14567) ### 🚩 Deprecations 🚩 - `pdata/pprofile`: Declare removed aggregation elements as deprecated. (#14528) ### 💡 Enhancements 💡 - `all`: Add detailed failure attributes to exporter send_failed metrics at detailed telemetry level. (#13956) The `otelcol_exporter_send_failed_{spans,metric_points,log_records}` metrics now include failure attributes when telemetry level is Detailed: `error.type` (OpenTelemetry semantic convention describing the error class) and `error.permanent` (indicates if error is permanent/non-retryable). The `error.type` attribute captures gRPC status codes (e.g., "Unavailable", "ResourceExhausted"), standard Go context errors (e.g., "canceled", "deadline_exceeded"), and collector-specific errors (e.g., "shutdown"). This enables better alerting and debugging by providing standardized error classification. - `cmd/builder`: Introduce new experimental `init` subcommand (#14530) The new `init` subcommand initializes a new custom collector - `cmd/builder`: Add "telemetry" field to allow configuring telemetry providers (#14575) Most users should not need to use this, this field should only be set if you intend to provide your own OpenTelemetry SDK. - `cmd/mdatagen`: Introduce additional metadata (the version since the deprecation started, and the deprecation reason) for deprecated metrics. (#14113) - `cmd/mdatagen`: Add optional `relationships` field to entity schema in metadata.yaml (#14284) - `exporter/debug`: Add `output_paths` configuration option to control output destination when `use_internal_logger` is false. (#10472) When `use_internal_logger` is set to `false`, the debug exporter now supports configuring the output destination via the `output_paths` option. This allows users to send debug exporter output to `stdout`, `stderr`, or a file path. The default value is `["stdout"]` to maintain backward compatibility. - `pkg/confmap`: Add experimental `ToStringMapRaw` function to decode `confmap.Conf` into a string map without losing internal types (#14480) This method exposes the internal structure of a `confmap.Conf` which may change at any time without prior notice ### 🧰 Bug fixes 🧰 - `cmd/mdatagen`: Reset aggDataPoints during metric init to avoid index out of range panic across emit cycles when reaggregation is enabled. (#14569) - `cmd/mdatagen`: Fix panic when mdatagen is run without arguments. (#14506) - `pdata/pprofile`: Fix off-by-one issue in dictionary lookups. (#14534) - `pkg/config/confighttp`: Fix high cardinality span name from request method from confighttp server internal telemetry (#14516) Follow spec to bound request method cardinality. - `pkg/otelcol`: Ignore component aliases in the `otelcol components` command (#14492) - `pkg/otelcol`: Order providers and converters in alphabetical order in the `components` subcommand. (#14476) ## v1.51.0/v0.145.0 ### 💡 Enhancements 💡 - `pkg/scraperhelper`: ScraperID has been added to the logs for metrics, logs, and profiles (#14461) ### 🧰 Bug fixes 🧰 - `exporter/otlp_grpc`: Fix the OTLP exporter balancer to use round_robin by default, as intended. (#14090) - `pkg/config/configoptional`: Fix `Unmarshal` methods not being called when config is wrapped inside `Optional` (#14500) This bug notably manifested in the fact that the `sending_queue::batch::sizer` config for exporters stopped defaulting to `sending_queue::sizer`, which sometimes caused the wrong units to be used when configuring `sending_queue::batch::min_size` and `max_size`. As part of the fix, `xconfmap` exposes a new `xconfmap.WithForceUnmarshaler` option, to be used in the `Unmarshal` methods of wrapper types like `configoptional.Optional` to make sure the `Unmarshal` method of the inner type is called. The default behavior remains that calling `conf.Unmarshal` on the `confmap.Conf` passed as argument to an `Unmarshal` method will skip any top-level `Unmarshal` methods to avoid infinite recursion in standard use cases. - `pkg/confmap`: Fix an issue where configs could fail to decode when using interpolated values in string fields. (#14413) For example, a header can be set via an environment variable to a string that is parseable as a number, e.g. `1234` - `pkg/service`: Don't error on startup when process metrics are enabled on unsupported OSes (e.g. AIX) (#14307) ## v1.50.0/v0.144.0 ### 🛑 Breaking changes 🛑 - `pkg/exporterhelper`: Change verbosity level for otelcol_exporter_queue_batch_send_size metric to detailed. (#14278) - `pkg/service`: Remove deprecated `telemetry.disableHighCardinalityMetrics` feature gate. (#14373) - `pkg/service`: Remove deprecated `service.noopTracerProvider` feature gate. (#14374) ### 🚩 Deprecations 🚩 - `exporter/otlp_grpc`: Rename `otlp` exporter to `otlp_grpc` exporter and add deprecated alias `otlp`. (#14403) - `exporter/otlp_http`: Rename `otlphttp` exporter to `otlp_http` exporter and add deprecated alias `otlphttp`. (#14396) ### 💡 Enhancements 💡 - `cmd/builder`: Avoid duplicate CLI error logging in generated collector binaries by relying on cobra's error handling. (#14317) - `cmd/mdatagen`: Add the ability to disable attributes at the metric level and re-aggregate data points based off of these new dimensions (#10726) - `cmd/mdatagen`: Add optional `display_name` and `description` fields to metadata.yaml for human-readable component names (#14114) The `display_name` field allows components to specify a human-readable name in metadata.yaml. When provided, this name is used as the title in generated README files. The `description` field allows components to include a brief description in generated README files. - `cmd/mdatagen`: Validate stability level for entities (#14425) - `pkg/xexporterhelper`: Reenable batching for profiles (#14313) - `receiver/nop`: add profiles signal support (#14253) ### 🧰 Bug fixes 🧰 - `pkg/exporterhelper`: Fix reference count bug in partition batcher (#14444) ## v1.49.0/v0.143.0 ### 💡 Enhancements 💡 - `all`: Update semconv import to 1.38.0 (#14305) - `exporter/nop`: Add profiles support to nop exporter (#14331) - `pkg/pdata`: Optimize the size and pointer bytes for pdata structs (#14339) - `pkg/pdata`: Avoid using interfaces/oneof like style for optional fields (#14333) ## v1.48.0/v0.142.0 ### 💡 Enhancements 💡 - `exporter/debug`: Add logging of dropped attributes, events, and links counts in detailed verbosity (#14202) - `extension/memory_limiter`: The memorylimiter extension can be used as an HTTP/GRPC middleware. (#14081) - `pkg/config/configgrpc`: Statically validate gRPC endpoint (#10451) This validation was already done in the OTLP exporter. It will now be applied to any gRPC client. - `pkg/service`: Add support to disabling adding resource attributes as zap fields in internal logging (#13869) Note that this does not affect logs exported through OTLP. ## v1.47.0/v0.141.0 ### 🛑 Breaking changes 🛑 - `pkg/config/confighttp`: Use configoptional.Optional for confighttp.ClientConfig.Cookies field (#14021) ### 💡 Enhancements 💡 - `pkg/config/confighttp`: Setting `compression_algorithms` to an empty list now disables automatic decompression, ignoring Content-Encoding (#14131) - `pkg/service`: Update semantic conventions from internal telemetry to v1.37.0 (#14232) - `pkg/xscraper`: Implement xscraper for Profiles. (#13915) ### 🧰 Bug fixes 🧰 - `pkg/config/configoptional`: Ensure that configoptional.None values resulting from unmarshaling are equivalent to configoptional.Optional zero value. (#14218) ## v1.46.0/v0.140.0 ### 💡 Enhancements 💡 - `cmd/mdatagen`: `metadata.yaml` now supports an optional `entities` section to organize resource attributes into logical entities with identity and description attributes (#14051) When entities are defined, mdatagen generates `AssociateWith{EntityType}()` methods on ResourceBuilder that associate resources with entity types using the entity refs API. The entities section is backward compatible - existing metadata.yaml files without entities continue to work as before. - `cmd/mdatagen`: Add semconv reference for metrics (#13920) - `connector/forward`: Add support for Profiles to Profiles (#14092) - `exporter/debug`: Disable sending queue by default (#14138) The recently added sending queue configuration in Debug exporter was enabled by default and had a problematic default size of 1. This change disables the sending queue by default. Users can enable and configure the sending queue if needed. - `pkg/config/configoptional`: Mark `configoptional.AddEnabledField` as beta (#14021) - `pkg/otelcol`: This feature has been improved and tested; secure-by-default redacts configopaque values (#12369) ### 🧰 Bug fixes 🧰 - `all`: Ensure service service.instance.id is the same for all the signals when it is autogenerated. (#14140) ## v1.45.0/v0.139.0 ### 🛑 Breaking changes 🛑 - `cmd/mdatagen`: Make stability.level a required field for metrics (#14070) - `cmd/mdatagen`: Replace `optional` field with `requirement_level` field for attributes in metadata schema (#13913) The `optional` boolean field for attributes has been replaced with a `requirement_level` field that accepts enum values: `required`, `conditionally_required`, `recommended`, or `opt_in`. - `required`: attribute is always included and cannot be excluded - `conditionally_required`: attribute is included by default when certain conditions are met (replaces `optional: true`) - `recommended`: attribute is included by default but can be disabled via configuration (replaces `optional: false`) - `opt_in`: attribute is not included unless explicitly enabled in user config When `requirement_level` is not specified, it defaults to `recommended`. - `pdata/pprofile`: Remove deprecated `PutAttribute` helper method (#14082) - `pdata/pprofile`: Remove deprecated `PutLocation` helper method (#14082) ### 💡 Enhancements 💡 - `all`: Add FIPS and non-FIPS implementations for allowed TLS curves (#13990) - `cmd/builder`: Set CGO_ENABLED=0 by default, add the `cgo_enabled` configuration to enable it. (#10028) - `pkg/config/configgrpc`: Errors of type status.Status returned from an Authenticator extension are being propagated as is to the upstream client. (#14005) - `pkg/config/configoptional`: Adds new `configoptional.AddEnabledField` feature gate that allows users to explicitly disable a `configoptional.Optional` through a new `enabled` field. (#14021) - `pkg/exporterhelper`: Replace usage of gogo proto for persistent queue metadata (#14079) - `pkg/pdata`: Remove usage of gogo proto and generate the structs with pdatagen (#14078) ### 🧰 Bug fixes 🧰 - `exporter/debug`: add queue configuration (#14101) ## v1.44.0/v0.138.0 ### 🛑 Breaking changes 🛑 - `all`: Remove deprecated type `TracesConfig` (#14036) - `pkg/exporterhelper`: Add default values for `sending_queue::batch` configuration. (#13766) Setting `sending_queue::batch` to an empty value now results in the same setup as the default batch processor configuration. - `all`: Add unified print-config command with mode support (redacted, unredacted), json support (unstable), and validation support. (#11775) This replaces the `print-initial-config` command. See the `service` package README for more details. The original command name `print-initial-config` remains an alias, to be retired with the feature flag. ### 💡 Enhancements 💡 - `all`: Add `keep_alives_enabled` option to ServerConfig to control HTTP keep-alives for all components that create an HTTP server. (#13783) - `pkg/otelcol`: Avoid unnecessary mutex in collector logs, replace by atomic pointer (#14008) - `cmd/mdatagen`: Add lint/ordering validation for metadata.yaml (#13781) - `pdata/xpdata`: Refactor JSON marshaling and unmarshaling to use `pcommon.Value` instead of `AnyValue`. (#13837) - `pkg/exporterhelper`: Expose `MergeCtx` in exporterhelper's queue batch settings` (#13742) ### 🧰 Bug fixes 🧰 - `all`: Fix zstd decoder data corruption due to decoder pooling for all components that create an HTTP server. (#13954) - `pkg/otelcol`: Remove UB when taking internal logs and move them to the final zapcore.Core (#14009) This can happen because of a race on accessing `logsTaken`. - `pkg/confmap`: Fix a potential race condition in confmap by closing the providers first. (#14018) ## v1.43.0/v0.137.0 ### 💡 Enhancements 💡 - `cmd/mdatagen`: Improve validation for resource attribute `enabled` field in metadata files (#12722) Resource attributes now require an explicit `enabled` field in metadata.yaml files, while regular attributes are prohibited from having this field. This improves validation and prevents configuration errors. - `all`: Changelog entries will now have their component field checked against a list of valid components. (#13924) This will ensure a more standardized changelog format which makes it easier to parse. - `pkg/pdata`: Mark featuregate pdata.useCustomProtoEncoding as stable (#13883) ## v1.42.0/v0.136.0 ### 💡 Enhancements 💡 - `xpdata`: Add Serialization and Deserialization of AnyValue (#12826) - `debugexporter`: add support for batching (#13791) The default queue size is 1 - `configtls`: Add early validation for TLS server configurations to fail fast when certificates are missing instead of failing at runtime. (#13130, #13245) - `mdatagen`: Expose stability level in generated metric documentation (#13748) - `internal/tools`: Add support for modernize in Makefile (#13796) ### 🧰 Bug fixes 🧰 - `otelcol`: Fix a potential deadlock during collector shutdown. (#13740) - `otlpexporter`: fix the validation of unix socket endpoints (#13826) ## v1.41.0/v0.135.0 ### 💡 Enhancements 💡 - `exporterhelper`: Add new `exporter_queue_batch_send_size` and `exporter_queue_batch_send_size_bytes` metrics, showing the size of telemetry batches from the exporter. (#12894) ## v1.40.0/v0.134.0 ### 💡 Enhancements 💡 - `pdata`: Add custom grpc/encoding that replaces proto and calls into the custom marshal/unmarshal logic in pdata. (#13631) This change should not affect other gRPC calls since it fallbacks to the default grpc/proto encoding if requests are not pdata/otlp requests. - `pdata`: Avoid copying the pcommon.Map when same origin (#13731) This is a very large improvement if using OTTL with map functions since it will avoid a map copy. - `exporterhelper`: Respect `num_consumers` when batching and partitioning are enabled. (#13607) ### 🧰 Bug fixes 🧰 - `pdata`: Correctly parse OTLP payloads containing non-packed repeated primitive fields (#13727, #13730) This bug prevented the Collector from ingesting most Histogram, ExponentialHistogram, and Profile payloads. ## v1.39.0/v0.133.0 ### 🛑 Breaking changes 🛑 - `all`: Increase minimum Go version to 1.24 (#13627) ### 💡 Enhancements 💡 - `otlphttpexporter`: Add `profiles_endpoint` configuration option to allow custom endpoint for profiles data export (#13504) The `profiles_endpoint` configuration follows the same pattern as `traces_endpoint`, `metrics_endpoint`, and `logs_endpoint`. When specified, profiles data will be sent to the custom URL instead of the default `{endpoint}/v1development/profiles`. - `pdata`: Add support for local memory pooling for data objects. (#13678) This is still an early experimental (alpha) feature. Do not recommended to be used production. To enable use "--featuregate=+pdata.useProtoPooling" - `pdata`: Optimize CopyTo messages to avoid any copy when same source and destination (#13680) - `receiverhelper`: New feature flag to make receiverhelper distinguish internal vs. downstream errors using new `otelcol_receiver_failed_x` and `otelcol_receiver_requests` metrics (#12207, #12802) 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 metrics. These new metrics and semantics are enabled through the `receiverhelper.newReceiverMetrics` feature gate. - `debugexporter`: Add support for entity references in debug exporter output (#13324) - `pdata`: Fix unnecessary allocation of a new state when adding new values to pcommon.Map (#13634) - `service`: Implement refcounting for pipeline data owned memory. (#13631) This feature is protected by `--featuregate=+pdata.useProtoPooling`. - `service`: Add a debug-level log message when a consumer returns an error. (#13357) - `xpdata`: Optimize xpdata/context for persistent queue when only one value for key (#13636) - `otlpreceiver`: Log the listening addresses of the receiver, rather than the configured endpoints. (#13654) - `pdata`: Use the newly added proto marshaler/unmarshaler for the official proto Marshaler/Unmarshaler (#13637) If any problems observed with this consider to disable the featuregate `--feature-gates=-pdata.useCustomProtoEncoding` - `configtls`: Enable X25519MLKEM768 as per draft-ietf-tls-ecdhe-mlkem (#13670) ### 🧰 Bug fixes 🧰 - `exporterhelper`: Prevent uncontrolled goroutines in batcher due to a incorrect worker pool behaviour. (#13689) - `service`: Ensure the insecure configuration is accounted for when normalizing the endpoint. (#13691) - `configoptional`: Allow validating nested types (#13579) `configoptional.Optional` now implements `xconfmap.Validator` - `batchprocessor`: Fix UB in batch processor when trying to read bytes size after adding request to pipeline (#13698) This bug only happens id detailed metrics are enabled and also an async (sending queue enabled) exporter that mutates data is configure. ## v1.38.0/v0.132.0 ### 🛑 Breaking changes 🛑 - `componentstatus`: Change the signature of the componentstatus.NewEvent to accept multiple options. (#13210) Changes the signature of the component.NewEvent to accept multiple EventBuilderOption, like the new WithAttributes constructor. ### 🚩 Deprecations 🚩 - `service`: move service.noopTraceProvider feature gate to deprecated stage (#13492) The functionality of the feature gate is available via configuration with the following telemetry settings: ``` service: telemetry: traces: level: none ``` - `mdatagen`: Remove the deletion of `generated_component_telemetry_test.go`. (#12067) This file used to be generated by mdatagen. Starting with 0.122.0, the code deletes that file. It is no longer necessary to delete the file, as code has had time to upgrade to mdatagen and delete the file. - `service`: The `telemetry.disableHighCardinalityMetrics` feature gate is deprecated (#13537) The feature gate is now deprecated since metric views can be configured. The feature gate will be removed in v0.134.0. The metric attributes removed by this feature gate are no longer emitted by the Collector by default, but if needed, you can achieve the same functionality by configuring the following metric views: ```yaml service: telemetry: metrics: level: detailed views: - selector: meter_name: "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" stream: attribute_keys: excluded: ["net.sock.peer.addr", "net.sock.peer.port", "net.sock.peer.name"] - selector: meter_name: "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" stream: attribute_keys: excluded: ["net.host.name", "net.host.port"] ``` Note that this requires setting `service::telemetry::metrics::level: detailed`. If you have a strong use case for using views in combination with a different level, please show your interest in https://github.com/open-telemetry/opentelemetry-collector/issues/10769. ### 💡 Enhancements 💡 - `pdata`: Generate Logs/Traces/Metrics/Profiles and p[log|trace|metric|profile]ExportResponse with pdatagen. (#13597) This change brings consistency on how these structs are written and remove JSON marshaling/unmarshaling hand written logic. - `confighttp`: Add option to configure ForceAttemptHTTP2 to support HTTP/1 specific transport settings. (#13426) - `pdata`: Avoid unnecessary buffer copy when JSON marshal fails. (#13598) - `cmd/mdatagen`: Use a custom host implementation for lifecycle tests (#13589) Use a custom noop host implementation that implements all non-deprecated, publicly-accessible interfaces implemented by the Collector service. - `processorhelper`: Add processor internal duration metric. (#13231) - `pdata`: Improve RemoveIf for slices to not reference anymore the removed memory (#13522) ### 🧰 Bug fixes 🧰 - `pdata`: Fix null pointer access when copying into a slice with larger cap but smaller len. (#13523) - `confighttp`: Fix middleware configuration field name from "middleware" to "middlewares" for consistency with configgrpc (#13444) - `memorylimiterextension, memorylimiterprocessor`: Memory limiter extension and processor shutdown don't throw an error if the component was not started first. (#9687) The components would throw an error if they were shut down before being started. With this change, they will no longer return an error, conforming to the lifecycle of components expected. - `confighttp`: Reuse zstd Reader objects (#11824) ## v1.37.0/v0.131.0 ### 🛑 Breaking changes 🛑 - `confighttp`: Move `confighttp.framedSnappy` feature gate to beta. (#10584) ### 💡 Enhancements 💡 - `exporter/debug`: Move to alpha stability except profiles (#13487) - `exporterhelper`: Enable `exporter.PersistRequestContext` feature gate by default. (#13437) Request context is now preserved by default when using persistent queues. Note that Auth extensions context is not propagated through the persistent queue. - `pdata`: Use pdatagen to generate marshalJSON without using gogo proto jsonpb. (#13450) - `otlpreceiver`: Remove usage of gogo proto which uses reflect.Value.MethodByName. Removes one source of disabling DCE. (#12747) - `exporterhelper`: Fix metrics split logic to consider metrics description into the size. (#13418) - `service`: New pipeline instrumentation now differentiates internal failures from downstream errors (#13234) With the telemetry.newPipelineTelemetry feature gate enabled, the "received" and "produced" metrics related to a component now distinguish between two types of errors: - "outcome = failure" indicates that the component returned an internal error; - "outcome = refused" indicates that the component successfully emitted data, but returned an error coming from a downstream component processing that data. - `pdata`: Remove usage of text/template from pdata, improves DCE. (#12747) - `architecture`: New Tier 3 platform riscv64 allowing the collector to be built and distributed for this platform. (#13462) ### 🧰 Bug fixes 🧰 - `exporterhelper`: Prevents the exporter for being stuck when telemetry data is bigger than batch.max_size (#12893) - `mdatagen`: Fix import paths for mdatagen component (#13069) - `otlpreceiver`: Error handler correctly fallbacks to content type (#13414) - `pdata/pprofiles`: Fix profiles JSON unmarshal logic for originalPayload. The bytes have to be base64 encoded. (#13483) - `xpdata`: Fix unmarshaling JSON for entities, add e2e tests to avoid this in the future. (#13480) - `service`: Downgrade dependency of prometheus exporter in OTel Go SDK (#13429) This fixes the bug where collector's internal metrics are emitted with an unexpected suffix in their names when users configure the service::telemetry::metrics::readers with Prometheus - `service`: Revert Default internal metrics config now enables `otel_scope_` labels (#12939, #13344) Reverting change temporarily due to prometheus exporter downgrade. This unfortunately re-introduces the bug that instrumentation scope attributes cause errors in Prometheus exporter. See http://github.com/open-telemetry/opentelemetry-collector/issues/12939 for details. - `builder`: Remove undocumented handling of `DIST_*` environment variables replacements (#13335) ## v1.36.1/v0.130.1 ### 🧰 Bug fixes 🧰 - `service`: Fixes bug where internal metrics are emitted with an unexpected suffix in their names when users configure `service::telemetry::metrics::readers` with Prometheus. (#13449) See more details on https://github.com/open-telemetry/opentelemetry-go/issues/7039 ## v1.36.0/v0.130.0 ### ❗ Known Issues ❗ - Due to a [bug](https://github.com/open-telemetry/opentelemetry-go/issues/7039) in the prometheus exporter, if you are configuring a prometheus exporter, the collector's internal metrics will be emitted with an unexpected suffix in its name. For example, the metric `otelcol_exporter_sent_spans__spans__total` instead of `otelcol_exporter_sent_spans_total`. The workaround is to manually configure `without_units: true` in your prometheus exporter config ```yaml service: telemetry: metrics: readers: - pull: exporter: prometheus: host: 0.0.0.0 port: 8888 without_units: true ``` If you are using the collector's default Prometheus exporter for exporting internal metrics you are unaffected. ### 🛑 Breaking changes 🛑 - `exporter/otlp`: Remove deprecated batcher config from OTLP, use queuebatch (#13339) ### 💡 Enhancements 💡 - `exporterhelper`: Enable items and bytes sizers for persistent queue (#12881) - `exporterhelper`: Refactor persistent storage size backup to always record it. (#12890) - `exporterhelper`: Add support to configure a different Sizer for the batcher than the queue (#13313) - `yaml`: Replaced `sigs.k8s.io/yaml` with `go.yaml.in/yaml` for improved support and long-term maintainability. (#13308) ### 🧰 Bug fixes 🧰 - `exporterhelper`: Fix exporter.PersistRequestContext feature gate (#13342) - `exporterhelper`: Preserve all metrics metadata when batch splitting. (#13236) Previously, when large batches of metrics were processed, the splitting logic in `metric_batch.go` could cause the `name` field of some metrics to disappear. This fix ensures that all metric fields are properly preserved when `metricRequest` objects are split. - `service`: Default internal metrics config now enables `otel_scope_` labels (#12939, #13344) By default, the Collector exports its internal metrics using a Prometheus exporter from the opentelemetry-go repository. With this change, the Collector no longer sets "without_scope_info" to true in its configuration. This means that all exported metrics will have `otel_scope_name`, `otel_scope_schema_url`, and `otel_scope_version` labels corresponding to the instrumentation scope metadata for that metric. This notably prevents an error when multiple metrics are only distinguished by their instrumentation scopes and end up aliased during export. If this is not desired behavior, a Prometheus exporter can be explicitly configured with this option enabled. ## v1.35.0/v0.129.0 ### 🛑 Breaking changes 🛑 - `exporterhelper`: Remove deprecated sending_queue::blocking options, use sending_queue::block_on_overflow. (#13211) ### 💡 Enhancements 💡 - `mdatagen`: Taught mdatagen to print the `go list` stderr output on failures, and to run `go list` where the metadata file is. (#13205) - `service`: Support setting `sampler` and `limits` under `service::telemetry::traces` (#13201) This allows users to enable sampling and set span limits on internal Collector traces using the OpenTelemetry SDK declarative configuration. - `pdata/pprofile`: Add new helper methods `FromLocationIndices` and `PutLocation` to read and modify the content of locations. (#13150) - `exporterhelper`: Preserve request span context and client information in the persistent queue. (#11740, #13220, #13232) It allows internal collector spans and client information to propagate through the persistent queue used by the exporters. The same way as it's done for the in-memory queue. Currently, it is behind the exporter.PersistRequestContext feature gate, which can be enabled by adding `--feature-gates=exporter.PersistRequestContext` to the collector command line. An exporter buffer stored by a previous version of the collector (or by a collector with the feature gate disabled) can be read by a newer collector with the feature enabled. However, the reverse is not supported: a buffer stored by a newer collector with the feature enabled cannot be read by an older collector (or by a collector with the feature gate disabled). ### 🧰 Bug fixes 🧰 - `pdata`: Fix copying of optional fields when the source is unset. (#13268) - `service`: Only allocate one set of internal log sampling counters (#13014) The case where logs are only exported to stdout was fixed in v0.126.0; this new fix also covers the case where logs are exported through OTLP. ## v1.34.0/v0.128.0 ### 🛑 Breaking changes 🛑 - `service/telemetry`: Mark "telemetry.disableAddressFieldForInternalTelemetry" as stable (#13152) ### 💡 Enhancements 💡 - `confighttp`: Update the HTTP server span naming to use the HTTP method and route pattern instead of the path. (#12468) The HTTP server span name will now be formatted as ` `. If a route pattern is not available, it will fall back to ``. - `service`: Use configured loggers to log errors as soon as it is available (#13081) - `service`: Remove stabilized featuregate useOtelWithSDKConfigurationForInternalTelemetry (#13152) ### 🧰 Bug fixes 🧰 - `telemetry`: Add generated resource attributes to the printed log messages. (#13110) If service.name, service.version, or service.instance.id are not specified in the config, they will be generated automatically. This change ensures that these attributes are also included in the printed log messages. - `mdatagen`: Fix generation when there are no events in the metadata. (#13123) - `confmap`: Do not panic on assigning nil maps to non-nil maps (#13117) - `pdata`: Fix event_name skipped when unmarshalling LogRecord from JSON (#13127) ## v1.33.0/v0.127.0 ### 🚩 Deprecations 🚩 - `semconv`: Deprecating the semconv package in favour of go.opentelemetry.io/otel/semconv (#13012) ### 💡 Enhancements 💡 - `exporter/debug`: Display resource and scope in `normal` verbosity (#10515) - `service`: Add size metrics defined in Pipeline Component Telemetry RFC (#13032) See [Pipeline Component Telemetry RFC](https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/rfcs/component-universal-telemetry.md) for more details: - `otelcol.receiver.produced.size` - `otelcol.processor.consumed.size` - `otelcol.processor.produced.size` - `otelcol.connector.consumed.size` - `otelcol.connector.produced.size` - `otelcol.exporter.consumed.size` ## v1.32.0/v0.126.0 ### 🛑 Breaking changes 🛑 - `configauth`: Removes deprecated `configauth.Authentication` and `extensionauthtest.NewErrorClient` (#12992) The following have been removed: - `configauth.Authentication` use `configauth.Config` instead - `extensionauthtest.NewErrorClient` use `extensionauthtest.NewErr` instead ### 💡 Enhancements 💡 - `service`: Replace `go.opentelemetry.io/collector/semconv` usage with `go.opentelemetry.io/otel/semconv` (#12991) - `confmap`: Update the behavior of the confmap.enableMergeAppendOption feature gate to merge only component lists. (#12926) - `service`: Add item count metrics defined in Pipeline Component Telemetry RFC (#12812) See [Pipeline Component Telemetry RFC](https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/rfcs/component-universal-telemetry.md) for more details: - `otelcol.receiver.produced.items` - `otelcol.processor.consumed.items` - `otelcol.processor.produced.items` - `otelcol.connector.consumed.items` - `otelcol.connector.produced.items` - `otelcol.exporter.consumed.items` - `tls`: Add trusted platform module (TPM) support to TLS authentication. (#12801) Now the TLS allows the use of TPM for loading private keys (e.g. in TSS2 format). ### 🧰 Bug fixes 🧰 - `exporterhelper`: Add validation error for batch config if min_size is greater than queue_size. (#12948) - `telemetry`: Allocate less memory per component when OTLP exporting of logs is disabled (#13014) - `confmap`: Use reflect.DeepEqual to avoid panic when confmap.enableMergeAppendOption feature gate is enabled. (#12932) - `internal telemetry`: Add resource attributes from telemetry.resource to the logger (#12582) Resource attributes from telemetry.resource were not added to the internal console logs. Now, they are added to the logger as part of the "resource" field. - `confighttp and configcompression`: Fix handling of `snappy` content-encoding in a backwards-compatible way (#10584, #12825) The collector used the Snappy compression type of "framed" to handle the HTTP content-encoding "snappy". However, this encoding is typically used to indicate the "block" compression variant of "snappy". This change allows the collector to: - When receiving a request with encoding 'snappy', the server endpoints will peek at the first bytes of the payload to determine if it is "framed" or "block" snappy, and will decompress accordingly. This is a backwards-compatible change. If the feature-gate "confighttp.framedSnappy" is enabled, you'll see new behavior for both client and server: - Client compression type "snappy" will now compress to the "block" variant of snappy instead of "framed". Client compression type "x-snappy-framed" will now compress to the "framed" variant of snappy. - Servers will accept both "snappy" and "x-snappy-framed" as valid content-encodings. - `tlsconfig`: Disable TPM tests on MacOS/Darwin (#12964) ## v1.31.0/v0.125.0 ### 🛑 Breaking changes 🛑 - `service`: Lowercase values for 'otelcol.component.kind' attributes. (#12865) - `service`: Restrict the `telemetry.newPipelineTelemetry` feature gate to metrics. (#12856, #12933) The "off" state of this feature gate introduced a regression, where the Collector's internal logs were missing component attributes. See issue #12870 for more details on this bug. On the other hand, the "on" state introduced an issue with the Collector's default internal metrics, because the Prometheus exporter does not currently support instrumentation scope attributes. To solve both of these issues, this change turns on the new scope attributes for logs and traces by default regardless of the feature gate. However, the new scope attributes for metrics stay locked behind the feature gate, and will remain off by default until the Prometheus exporter is updated to support scope attributes. Please understand that enabling the `telemetry.newPipelineTelemetry` feature gate may break the export of Collector metrics through, depending on your configuration. Having a `batch` processor in multiple pipelines is a known trigger for this. This comes with a breaking change, where internal logs exported through OTLP will now use instrumentation scope attributes to identify the source component instead of log attributes. This does not affect the Collector's stderr output. See the changelog for v0.123.0 for a more detailed description of the gate's effects. ### 💡 Enhancements 💡 - `mdatagen`: Add support for attributes for telemetry configuration in metadata. (#12919) - `configmiddleware`: Add extensionmiddleware interface. (#12603, #9591) - `configgrpc`: Add gRPC middleware support. (#12603, #9591) - `confighttp`: Add HTTP middleware support. (#12603, #9591, #7441) - `configmiddleware`: Add configmiddleware struct. (#12603, #9591) ### 🧰 Bug fixes 🧰 - `exporterhelper`: Do not ignore the `num_consumers` setting when batching is enabled. (#12244) - `exporterhelper`: Reject elements larger than the queue capacity (#12847) - `mdatagen`: Add time and plog package imports (#12907) - `confmap`: Maintain nil values when marshaling or unmarshaling nil slices (#11882) Previously, nil slices were converted to empty lists, which are semantically different than a nil slice. This change makes this conversion more consistent when encoding or decoding config, and these values are now maintained. ## v1.30.0/v0.124.0 ### 💡 Enhancements 💡 - `exporterhelper`: Add support for bytes-based batching for profiles in the exporterhelper package. (#3262) - `otelcol`: Enhance config validation using command to capture all validation errors that prevents the collector from starting. (#8721) - `exporterhelper`: Link batcher context to all batched request's span contexts. (#12212, #8122) ### 🧰 Bug fixes 🧰 - `confighttp`: Ensure http authentication server failures are handled by the provided error handler (#12666) ## v1.29.0/v0.123.0 ### ❗ Known Issues ❗ - This version increases memory usage by ~0.5 MB per component in the pipelines because a separate Zap Core logger is initialized for each component. The issue is partially fixed in v0.126.0 for users who write logs to stdout, but do not export logs via OTLP. See https://github.com/open-telemetry/opentelemetry-collector/issues/13014 for more details. ### 🛑 Breaking changes 🛑 - `service/telemetry`: Mark `telemetry.disableAddressFieldForInternalTelemetry` as beta, usage of deprecated service::telemetry::address are ignored (#25115) To restore the previous behavior disable `telemetry.disableAddressFieldForInternalTelemetry` feature gate. - `exporterbatch`: Remove deprecated fields `min_size_items` and `max_size_items` from batch config. (#12684) ### 🚩 Deprecations 🚩 - `otlpexporter`: Mark BatcherConfig as deprecated, use `sending_queue::batch` instead (#12726) - `exporterhelper`: Deprecate `blocking` in favor of `block_on_overflow`. (#12710) - `exporterhelper`: Deprecate configuring exporter batching separately. Use `sending_queue::batch` instead. (#12772) Moving the batching configuration to `sending_queue::batch` requires setting `sending_queue::sizer` to `items` which means that `sending_queue::queue_size` needs to be also increased by the average batch size number (roughly x5000 for the default batching configuration). See https://github.com/open-telemetry/opentelemetry-collector/tree/main/exporter/exporterhelper#configuration ### 💡 Enhancements 💡 - `exporterhelper`: Add support to configure batching in the sending queue. (#12746) - `exporterhelper`: Add support for wait_for_result, remove disabled_queue (#12742) This has a side effect for users of the experimental BatchConfig with the queue disabled, since not this is | uses only NumCPU() consumers. - `exporterhelper`: Allow exporter memory queue to use different type of sizers. (#12708) - `service`: Add "telemetry.newPipelineTelemetry" feature gate to inject component-identifying attributes in internal telemetry (#12217) With the feature gate enabled, all internal telemetry (metrics/traces/logs) will include some of the following instrumentation scope attributes: - `otelcol.component.kind` - `otelcol.component.id` - `otelcol.pipeline.id` - `otelcol.signal` - `otelcol.signal.output` These attributes are defined in the [Pipeline Component Telemetry RFC](https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/rfcs/component-universal-telemetry.md#attributes), and identify the component instance from which the telemetry originates. They are added automatically without changes to component code. These attributes were already included in internal logs as regular log attributes, starting from v0.120.0. For consistency with other signals, they have been switched to scope attributes (with the exception of logs emitted to standard output), and are now enabled by the feature gate. Please make sure that the exporter / backend endpoint you use has support for instrumentation scope attributes before using this feature. If the internal telemetry is exported to another Collector, a transform processor could be used to turn them into other kinds of attributes if necessary. - `exporterhelper`: Enable support to do batching using `bytes` sizer (#12751) - `service`: Add config key to set metric views used for internal telemetry (#10769) The `service::telemetry::metrics::views` config key can now be used to explicitly set the list of metric views used for internal telemetry, mirroring `meter_provider::views` in the SDK config. This can be used to disable specific internal metrics, among other uses. This key will cause an error if used alongside other features which would normally implicitly create views, such as: - not setting `service::telemetry::metrics::level` to `detailed`; - enabling the `telemetry.disableHighCardinalityMetrics` feature flag. ### 🧰 Bug fixes 🧰 - `exporterhelper`: Fix order of starting between queue and batch. (#12705) ## v1.28.1/v0.122.1 ### 🧰 Bug fixes 🧰 - `confmap`: Ensure slices with defaults containing struct values are correctly set. (#12661) This reverts the changes made in https://github.com/open-telemetry/opentelemetry-collector/pull/11882. ## v1.28.0/v0.122.0 ### 🛑 Breaking changes 🛑 - `service`: Batch processor telemetry is no longer emitted at "basic" verbosity level (#7890) According to the guidelines, basic-level telemetry should be reserved for core Collector APIs. Components such as the batch processor should emit telemetry starting from the "normal" level (which is also the default level). Migration: If your Collector telemetry was set to `level: basic` and you want to keep seeing batch processor-related metrics, consider switching to `level: normal` instead. ### 💡 Enhancements 💡 - `service`: Add `service.AllowNoPipelines` feature gate to allow starting the Collector without pipelines. (#12613) This can be used to start with only extensions. - `mdatagen`: Delete generated_status.go if the component type doesn't require it. (#12346) - `componenttest`: Improve config struct mapstructure field tag checks (#12590) `remain` tags and `omitempty` tags without a custom field name will now pass validation. - `service`: include component id/type in start error (#10426) - `mdatagen`: Add deprecation date and migration guide fields as part of component metadata (#12359) - `confmap`: Introduce a new feature flag to allow for merging lists instead of discarding the existing ones. (#8394, #8754, #10370) You can enable this option via the command line by running following command: otelcol --config=main.yaml --config=extra_config.yaml --feature-gates=-confmap.enableMergeAppendOption - `zpagesextension`: Add expvar handler to zpages extension. (#11081) ### 🧰 Bug fixes 🧰 - `confmap`: Maintain nil values when marshaling or unmarshaling nil slices (#11882) Previously, nil slices were converted to empty lists, which are semantically different than a nil slice. This change makes this conversion more consistent when encoding or decoding config, and these values are now maintained. - `service`: do not attempt to register process metrics if they are disabled (#12098) ## v1.27.0/v0.121.0 ### 🛑 Breaking changes 🛑 - `confighttp`: Make the client config options `max_idle_conns`, `max_idle_conns_per_host`, `max_conns_per_host`, and `idle_conn_timeout` integers (#9478) All four options can be set to `0` where they were previously set to `null` ### 🚩 Deprecations 🚩 - `exporterhelper`: Deprecate `min_size_items` and `max_size_items` in favor of `min_size` and `max_size`. (#12486) ### 💡 Enhancements 💡 - `mdatagen`: Add `converter` and `provider` module classes (#12467) - `pipeline`: output pipeline name with signal as signal[/name] format in logs. (#12410) - `memorylimiter`: Add support to configure min GC intervals for soft and hard limits. (#12450) - `otlpexporter`: Update the stability level for logs, it has been as stable as traces and metrics for some time. (#12423) - `service`: Create a new subcommand to dump the initial configuration after resolving/merging. (#11479) To use the `print-initial-config` subcommand, invoke the Collector with the subcommand and corresponding feature gate: `otelcol print-initial-config --feature-gates=otelcol.printInitialConfig --config=config.yaml`. Note that the feature gate enabling this flag is currently in alpha stability, and the subcommand may be changed in the future. - `memorylimiterprocessor`: Add support for profiles. (#12453) - `otelcol`: Converters are now available in the `components` command. (#11900, #12385) - `component`: Mark module as stable (#9376) - `confmap`: Surface YAML parsing errors when they happen at the top-level. (#12180) This adds context to some instances of the error "retrieved value (type=string) cannot be used as a Conf", which typically happens because of invalid YAML documents - `pprofile`: Add LinkIndex attribute to the generated Sample type (#12485) - `exporterhelper`: Stabilize exporter.UsePullingBasedExporterQueueBatcher and remove old batch sender (#12425) - `mdatagen`: Update metadata schema with new fields without enforcing them (#12359) ### 🧰 Bug fixes 🧰 - `service`: Fix crash at startup when converting from v0.2.0 to v0.3.0 (#12438) - `service`: fix bug in parsing service::telemetry configuration (#12437) - `exporterhelper`: Fix bug where the error logged when conversion of data fails is always nil (#12510) - `mdatagen`: Adds back missing import for filter when emitting resource attributes (#12455) ## v1.26.0/v0.120.0 ### 🛑 Breaking changes 🛑 - `all`: Added support for go1.24, bumped minimum version to 1.23 (#12370) - `mdatagen`: Removing deprecated generated funcs and a few test funcs as well. (#12304) - `service`: Align component logger attributes with those defined in RFC (#12217) See [Pipeline Component Telemetry RFC](https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/rfcs/component-universal-telemetry.md#attributes) ### 💡 Enhancements 💡 - `otlpreceiver`: Update stability for logs (#12335) - `exporterhelper`: Implement sync disabled queue used when batching is enabled. (#12245) - `exporterhelper`: Enable the new pull-based batcher in exporterhelper (#12291) - `exporterhelper`: Update queue size after the element is done exported (#12399) After this change the active queue size will include elements in the process of being exported. - `otelcol`: Add featuregate command to display information about available features (#11998) The featuregate command allows users to view detailed information about feature gates including their status, stage, and description. ### 🧰 Bug fixes 🧰 - `memorylimiter`: Logger no longer attributes to single signal, pipeline, or component. (#12217) - `otlpreceiver`: Logger no longer attributes to random signal when receiving multiple signals. (#12217) - `exporterhelper`: Fix undefined behavior access to request after send to next component. This causes random memory access. (#12281) - `exporterhelper`: Fix default batcher to correctly call all done callbacks exactly once (#12247) - `otlpreceiver`: Fix OTLP http receiver to correctly set Retry-After (#12367) - `otlphttpexporter`: Fix parsing logic for Retry-After in OTLP http protocol. (#12366) The value of Retry-After field can be either an HTTP-date or delay-seconds and the current logic only parsed delay-seconds. - `cmd/builder`: Ensure unique aliases for modules with same suffix (#12201) ## v1.25.0/v0.119.0 ### 🛑 Breaking changes 🛑 - `exporterhelper`: Rename exporter span signal specific attributes (e.g. "sent_spans" / "send_failed_span") to "items.sent" / "items.failed". (#12165) - `cmd/mdatagen`: Remove dead field `telemetry::level` (#12144) - `exporterhelper`: Change exporter ID to be a Span level attribute instead on each event. (#12164) This does not have an impact on the level of information emitted, but on the structure of the Span. - `cmd/mdatagen`: Remove `level` field from metrics definition (#12145) This mechanism will be added back once a new views mechanism is implemented. - `service`: Value for telemetry exporter `otlp.protocol` updated from `grpc/protobuf` to `grpc`. (#12337) - `service`: internal metrics exported over Prometheus may differ from previous versions. (#11611) Users who do not customize the Prometheus reader should not be impacted. The change to update the internal telemetry to use [otel-go config](https://pkg.go.dev/go.opentelemetry.io/contrib/config) can cause unexpected behaviour for end users. This change is caused by the default values in `config` being different from what the Collector has used in previous versions. The following changes can occur when users configure their `service::telemetry::metrics::readers`: - the metric name will append a `_total` suffix if `without_type_suffix` is not configured. Set `without_type_suffix` to `true` to disable this. - units will be appended to metric name if `without_units` is not configured. Set `without_units` to `true` to disable this. - a `target_info` metric will be emitted if `without_scope_info` is not configured. Set `without_scope_info` to `true` to disable this. ### 💡 Enhancements 💡 - `configtls`: Allow users to mention their preferred curve types for ECDHE handshake (#12174) - `service`: remove custom code and instead use config package to instantiate meter provider. (#11611) - `otelcol`: Adds support for listing config providers in components command's output (#11570) - `general`: Reduce memory allocations when loading configuration and parsing component names (#11964) ### 🧰 Bug fixes 🧰 - `exporterhelper`: Fix bug that the exporter with new batcher may have been marked as non mutation. (#12239) Only affects users that manually turned on `exporter.UsePullingBasedExporterQueueBatcher` featuregate. - `service`: Preserve URL normalization logic that was present before. (#12254) - `confighttp`: confighttp.ToServer now sets ErrorLog with a default logger backed by Zap (#11820) This change ensures that the http.Server's ErrorLog is correctly set using Zap's logger at the error level, addressing the issue of error logs being printed using a different logger. - `exporterhelper`: Fix context propagation for DisabledBatcher (#12231) - `mdatagen`: apply fieldalignment to generated code (#12125) - `mdatagen`: Fix bug where Histograms were marked as not supporting temporal aggregation (#12168) - `exporterhelper`: Fix MergeSplit issue that ignores the initial message size. (#12257) - `service`: Include validation errors from telemetry.Config when validating the service config (#12100) Previously validation errors were only printed to the console - `service-telemetry`: pass the missing async error channel into service telemetry settings (#11417) ## v1.24.0/v0.118.0 ### 💡 Enhancements 💡 - `exporterhelper`: Add blocking option to control queue behavior when full (#12090) - `debugexporter`: Add EventName to debug exporter for Logs. EventName was added as top-level field in the LogRecord from 1.5.0 of proto definition. (#11966) - `confighttp`: Added support for configuring compression levels. (#10467) A new configuration option called CompressionParams has been added to confighttp. | This allows users to configure the compression levels for the confighttp client. - `exporterhelper`: Change the memory queue implementation to not pre-allocate capacity objects. (#12070) This change improves memory usage of the collector under low utilization and is a prerequisite for supporting different other size limitations (number of items, bytes). ### 🧰 Bug fixes 🧰 - `mdatagen`: apply fieldalignment to generated code (#12121) - `otelcoltest`: Set `DefaultScheme` to `env` in the test `ConfigProvider` to replicate the default provider used by the Collector. (#12066) ## v1.23.0/v0.117.0 ### 🛑 Breaking changes 🛑 - `otelcol`: Remove warnings when 0.0.0.0 is used (#11713, #8510) ### 🧰 Bug fixes 🧰 - `internal/sharedcomponent`: Fixed bug where sharedcomponent would use too much memory remembering all the previously reported statuses (#11826) ## v1.22.0/v0.116.0 ### 🛑 Breaking changes 🛑 - `pdata/pprofile`: Remove deprecated `Profile.EndTime` and `Profile.SetEndTime` methods. (#11796) ### 💡 Enhancements 💡 - `xconfighttp`: Add WithOtelHTTPOptions to experimental module xconfighttp (#11770) ### 🧰 Bug fixes 🧰 - `exporterhelper`: Fix memory leak at exporter shutdown (#11401) - `sharedcomponent`: Remove race-condition and cleanup locking (#11819) ## v1.21.0/v0.115.0 ### 🛑 Breaking changes 🛑 - `otelcol`: Change all logged timestamps to ISO8601. (#10543) This makes log timestamps human-readable (as opposed to epoch seconds in scientific notation), but may break users trying to parse logged lines in the old format. - `pdata/pprofile`: Upgrade pdata to opentelemetry-proto v1.4.0 (#11722) ### 🚩 Deprecations 🚩 - `scraperhelper`: Deprecate all Scraper helpers in scraperhelper (#11732) Deprecate ScrapeFunc, ScraperOption, WithStart, WithShutdown in favor of equivalent funcs in scraper package. ### 💡 Enhancements 💡 - `exporterqueue`: Introduce a feature gate exporter.UsePullingBasedExporterQueueBatcher to use the new pulling model in exporter queue batching. (#8122, #10368) If both queuing and batching is enabled for exporter, we now use a pulling model instead of a pushing model. num_consumer in queue configuration is now used to specify the maximum number of concurrent workers that are sending out the request. - `service`: label metrics as alpha to communicate their stability (#11729) - `consumer`: Mark consumer as stable. (#9046) - `service`: Add support for ca certificates in telemetry metrics otlp grpc exporter (#11633) Before this change the Certificate value in config was silently ignored. ### 🧰 Bug fixes 🧰 - `service`: ensure OTLP emitted logs respect severity (#11718) - `featuregate`: Fix an unfriendly display message `runtime error` when featuregate is used to display command line usage. (#11651) - `profiles`: Fix iteration over scope profiles while counting the samples. (#11688) ## v1.20.0/v0.114.0 ### 💡 Enhancements 💡 - `cmd/builder`: Allow for replacing of local Providers and Converters when building custom collector with ocb. (#11649) Use the property `path` under `gomod` to replace an go module with a local folder in builder-config.yaml. Ex: ``` providers: - gomod: module.url/my/custom/provider v1.2.3 path: /path/to/local/provider ``` - `cmd/builder`: Allow configuring `confmap.Converter` components in ocb. (#11582) If no converters are specified, there will be no converters added. Currently, the only published converter is `expandconverter` which is deprecated as of v0.107.0, but can still be added for testing purposes. To configure a custom converter, make sure your converter implements the converter interface and is published as a go module (or replaced locally if not published). You may then use the `converters` key in your OCB build manifest with a list of Go modules (and replaces as necessary) to include your converter. Please note that converters are order-dependent. The confmap will apply converters in order of which they are listed in your manifest if there is more than one. - `all`: shorten time period before removing an unmaintained component from 6 months to 3 months (#11664) ### 🧰 Bug fixes 🧰 - `all`: Updates dialer timeout section documentation in confignet README (#11685) - `scraperhelper`: If the scraper shuts down, do not scrape first. (#11632) When the scraper is shutting down, it currently will scrape at least once. With this change, upon receiving a shutdown order, the receiver's scraperhelper will exit immediately. ## v1.19.0/v0.113.0 ### 🛑 Breaking changes 🛑 - `internal/fanoutconsumer`: Extract internal/fanoutconsumer as a separate go module (#11441) - `builder`: Remove builder support to build old version, and the otelcol_version config (#11405) User should remove this property from their config, to build older versions use older builders. - `receiver`: Make receivertest into its own module (#11462) - `builder`: Remove deprecated flags from Builder (#11576) Here is the list of flags | --name, --description, --version, --otelcol-version, --go, --module - `internal/sharedcomponent`: Extract internal/sharedcomponent as a separate go module (#11442) ### 💡 Enhancements 💡 - `mdatagen`: Add otlp as supported distribution (#11527) - `batchprocessor`: Move single shard batcher creation to the constructor (#11594) - `service`: add support for using the otelzap bridge and emit logs using the OTel Go SDK (#10544) ### 🧰 Bug fixes 🧰 - `service`: ensure traces and logs emitted by the otel go SDK use the same resource information (#11578) - `config/configgrpc`: Patch for bug in the grpc-go NewClient that makes the way the hostname is resolved incompatible with the way proxy setting are applied. (#11537) - `builder`: Update builder default providers to latest stable releases (#11566) ## v1.18.0/v0.112.0 ### 🛑 Breaking changes 🛑 - `consumer/consumererror`: Extract consumer/consumererror as a separate go module (#11440) - `exporter/exportertest`: Put exportertest into its own module (#11461) - `service`: Remove stable gate component.UseLocalHostAsDefaultHost (#11412) ### 🚩 Deprecations 🚩 - `processortest`: Deprecated 'NewUnhealthyProcessorCreateSettings'. Use NewNopSettings instead. (#11307) ### 💡 Enhancements 💡 - `mdatagen`: Added generated_package_name config field to support custom generated package name. (#11231) - `mdatagen`: Generate documentation for components with resource attributes only (#10705) - `confighttp`: Adding support for lz4 compression into the project (#9128) - `service`: Hide profiles support behind a feature gate while it remains alpha. (#11477) - `exporterhelper`: Retry sender will fail fast when the context timeout is shorter than the next retry interval. (#11183) ### 🧰 Bug fixes 🧰 - `cmd/builder`: Fix default configuration for builder for httpprovider, httpsprovider, and yamlprovider. (#11357) - `processorhelper`: Fix issue where in/out parameters were not recorded when error was returned from consumer. (#11351) ## v1.17.0/v0.111.0 ### 🛑 Breaking changes 🛑 - `service/telemetry`: Change default metrics address to "localhost:8888" instead of ":8888" (#11251) This behavior can be disabled by disabling the feature gate 'telemetry.UseLocalHostAsDefaultMetricsAddress'. - `loggingexporter`: Removed the deprecated logging exporter. Use the debug exporter instead. (#11037) ### 🚩 Deprecations 🚩 - `service/telemetry`: Deprecate service::telemetry::metrics::address in favor of service::telemetry::metrics::readers. (#11205) - `processorhelper`: Deprecate BuildProcessorMetricName as it's no longer needed since introduction of mdatagen (#11302) ### 💡 Enhancements 💡 - `ocb`: create docker images for OCB, per https://github.com/open-telemetry/opentelemetry-collector-releases/pull/671 (#5712) Adds standard Docker images for OCB to Dockerhub and GitHub, see hub.docker.com/r/otel/opentelemetry-collector-builder - `confighttp`: Snappy compression to lazy read for memory efficiency (#11177) - `httpsprovider`: Mark the httpsprovider as stable. (#11191) - `httpprovider`: Mark the httpprovider as stable. (#11191) - `yamlprovider`: Mark the yamlprovider as stable. (#11192) - `confmap`: Allow using any YAML structure as a string when loading configuration including time.Time formats (#10659) Previously, fields with time.Time formats could not be used as strings in configurations ### 🧰 Bug fixes 🧰 - `processorhelper`: Fix data race condition, concurrent writes to the err variable, causes UB (Undefined Behavior) (#11350) - `cmd/builder`: re-adds function to properly set and view version number of OpenTelemetry Collector Builder (ocb) binaries (#11208) - `pdata`: Unmarshal Span and SpanLink flags from JSON (#11267) ## v1.16.0/v0.110.0 ### 🛑 Breaking changes 🛑 - `processorhelper`: Update incoming/outgoing metrics to a single metric with a `otel.signal` attributes. (#11144) The following metrics were added in the previous version - otelcol_processor_incoming_spans - otelcol_processor_outgoing_spans - otelcol_processor_incoming_metric_points - otelcol_processor_outgoing_metric_points - otelcol_processor_incoming_log_records - otelcol_processor_outgoing_log_records They are being replaced with the following to more closely align with OTEP 259: - otelcol_processor_incoming_items - otelcol_processor_outgoing_items - `processorhelper`: Remove deprecated `[Traces|Metrics|Logs]`Inserted funcs (#11151) - `config`: Mark UseLocalHostAsDefaultHostfeatureGate as stable (#11235) ### 🚩 Deprecations 🚩 - `processorhelper`: deprecate accepted/refused/dropped metrics (#11201) The following metrics are being deprecated as they were only used in a single processor: - `otelcol_processor_accepted_log_records` - `otelcol_processor_accepted_metric_points` - `otelcol_processor_accepted_spans` - `otelcol_processor_dropped_log_records` - `otelcol_processor_dropped_metric_points` - `otelcol_processor_dropped_spans` - `otelcol_processor_refused_log_records` - `otelcol_processor_refused_metric_points` - `otelcol_processor_refused_spans` ### 💡 Enhancements 💡 - `pdata`: Add support to MoveTo for Map, allow avoiding copies (#11175) - `mdatagen`: Add stability field to telemetry metrics, allowing the generated description to include a stability string. (#11160) - `confignet`: Mark module as Stable. (#9801) - `confmap/provider/envprovider`: Support default values when env var is empty (#5228) - `mdatagen`: mdatagen `validateMetrics()` support validate metrics in `telemetry.metric` (#10925) - `service/telemetry`: Mark useOtelWithSDKConfigurationForInternalTelemetry as stable (#7532) - `mdatagen`: Use cobra for the command, add version flag (#11196) ### 🧰 Bug fixes 🧰 - `service`: Ensure process telemetry is registered when internal telemetry is configured with readers instead of an address. (#11093) - `mdatagen`: Fix incorrect generation of metric tests with boolean attributes. (#11169) - `otelcol`: Fix the Windows Event Log configuration when running the Collector as a Windows service. (#5297, #11051) - `builder`: Honor build_tags in config (#11156) - `builder`: Fix version for providers in the default config (#11123) - `cmd/builder`: Temporarily disable strict versioning checks (#11129, #11152) The strict versioning check may be enabled by default in a future version once all configuration providers are stabilized. - `confmap`: Fix loading config of a component from a different source. (#11154) This issue only affected loading the whole component config, loading parts of a component config from a different source was working correctly. ## v1.15.0/v0.109.0 ### 🛑 Breaking changes 🛑 - `scraperhelper`: Remove deprecated `ObsReport`, `ObsReportSettings`, `NewObsReport` types/funcs (#11086) - `confmap`: Remove stable `confmap.strictlyTypedInput` gate (#11008) - `confmap`: Removes stable `confmap.unifyEnvVarExpansion` feature gate. (#11007) - `ballastextension`: Removes the deprecated ballastextension (#10671) - `service`: Removes stable `service.disableOpenCensusBridge` feature gate (#11009) ### 🚩 Deprecations 🚩 - `processorhelper`: These funcs are not used anywhere, marking them deprecated. (#11083) ### 🚀 New components 🚀 - `extension/experimental/storage`: Move `extension/experimental/storage` into a separate module (#11022) ### 💡 Enhancements 💡 - `configtelemetry`: Add guidelines for each level of component telemetry (#10286) - `service`: move `useOtelWithSDKConfigurationForInternalTelemetry` gate to beta (#11091) - `service`: implement a no-op tracer provider that doesn't propagate the context (#11026) The no-op tracer provider supported by the SDK incurs a memory cost of propagating the context no matter what. This is not needed if tracing is not enabled in the Collector. This implementation of the no-op tracer provider removes the need to allocate memory when tracing is disabled. - `envprovider`: Mark module as stable (#10982) - `fileprovider`: Mark module as stable (#10983) - `processor`: Add incoming and outgoing counts for processors using processorhelper. (#10910) Any processor using the processorhelper package (this is most processors) will automatically report incoming and outgoing item counts. The new metrics are: - otelcol_processor_incoming_spans - otelcol_processor_outgoing_spans - otelcol_processor_incoming_metric_points - otelcol_processor_outgoing_metric_points - otelcol_processor_incoming_log_records - otelcol_processor_outgoing_log_records ### 🧰 Bug fixes 🧰 - `configgrpc`: Change the value of max_recv_msg_size_mib from uint64 to int to avoid a case where misconfiguration caused an integer overflow. (#10948) - `exporterqueue`: Fix a bug in persistent queue that Offer can becomes deadlocked when queue is almost full (#11015) ## v1.14.1/v0.108.1 ### 🧰 Bug fixes 🧰 - `mdatagen`: Fix a missing import in the generated test file (#10969) ## v1.14.0/v0.108.0 ### 🛑 Breaking changes 🛑 - `all`: Added support for go1.23, bumped the minimum version to 1.22 (#10869) - `otelcol`: Remove deprecated `ConfmapProvider` interface. (#10934) - `confmap`: Mark `confmap.strictlyTypedInput` as stable (#10552) ### 💡 Enhancements 💡 - `exporter/otlp`: Add batching option to otlp exporter (#8122) - `builder`: Add a --skip-new-go-module flag to skip creating a module in the output directory. (#9252) - `component`: Add `TelemetrySettings.LeveledMeterProvider` func to replace MetricsLevel in the near future (#10931) - `mdatagen`: Add `LeveledMeter` method to mdatagen (#10933) - `service`: Adds `level` configuration option to `service::telemetry::trace` to allow users to disable the default TracerProvider (#10892) This replaces the feature gate `service.noopTracerProvider` introduced in v0.107.0 - `componentstatus`: Add new Reporter interface to define how to report a status via a `component.Host` implementation (#10852) - `mdatagen`: support using a different github project in mdatagen README issues list (#10484) - `mdatagen`: Updates mdatagen's usage to output a complete command line example, including the metadata.yaml file. (#10886) - `extension`: Add ModuleInfo to extension.Settings to allow extensions to access component go module information. (#10876) - `confmap`: Mark module as stable (#9379) ### 🧰 Bug fixes 🧰 - `batchprocessor`: Update units for internal telemetry (#10652) - `confmap`: Fix bug where an unset env var used with a non-string field resulted in a panic (#10950) - `service`: Fix memory leaks during service package shutdown (#9165) - `mdatagen`: Update generated telemetry template to only include context import when there are async metrics. (#10883) - `mdatagen`: Fixed bug in which setting `SkipLifecycle` & `SkipShutdown` to true would result in a generated file with an unused import `confmaptest` (#10866) - `confmap`: Use string representation for field types where all primitive types are strings. (#10937) - `otelcol`: Preserve internal representation when unmarshaling component configs (#10552) ## v1.13.0/v0.107.0 ### 🛑 Breaking changes 🛑 - `service`: Remove OpenCensus bridge completely, mark feature gate as stable. (#10414) - `confmap`: Set the `confmap.unifyEnvVarExpansion` feature gate to Stable. Expansion of `$FOO` env vars is no longer supported. Use `${FOO}` or `${env:FOO}` instead. (#10508) - `service`: Remove `otelcol` from Prometheus configuration. This means that any metric that isn't explicitly prefixed with `otelcol_` no longer have that prefix. (#9759) ### 💡 Enhancements 💡 - `mdatagen`: export ScopeName in internal/metadata package (#10845) This can be used by components that need to set their scope name manually. Will save component owners from having to store a variable, which may diverge from the scope name used by the component for emitting its own telemetry. - `semconv`: Add v1.26.0 semantic conventions package (#10249, #10829) - `mdatagen`: Expose a setting on tests::host to set up your own host initialization code (#10765) Some receivers require a host that has additional capabilities such as exposing exporters. For those, we can expose a setting that allows them to place a different host in the generated code. - `confmap`: Allow using any YAML structure as a string when loading configuration. (#10800) Previous to this change, slices could not be used as strings in configuration. - `ocb`: migrate build and release of ocb binaries to opentelemetry-collector-releases repository (#10710) ocb binaries will now be released under open-telemetry/opentelemetry-collector-releases tagged as "cmd/builder/vX.XXX.X" - `semconv`: Add semantic conventions version v1.27.0 (#10837) - `client`: Mark module as stable. (#10775) ### 🧰 Bug fixes 🧰 - `configtelemetry`: Add 10s read header timeout on the configtelemetry Prometheus HTTP server. (#5699) - `service`: Allow users to disable the tracer provider via the feature gate `service.noopTracerProvider` (#10858) The service is returning an instance of a SDK tracer provider regardless of whether there were any processors configured causing resources to be consumed unnecessarily. - `processorhelper`: Fix processor metrics not being reported initially with 0 values. (#10855) - `service`: Implement the `temporality_preference` setting for internal telemetry exported via OTLP (#10745) - `configauth`: Fix unmarshaling of authentication in HTTP servers. (#10750) - `confmap`: If loading an invalid YAML string through a provider, use it verbatim instead of erroring out. (#10759) This makes the ${env:ENV} syntax closer to how ${ENV} worked before unifying syntaxes. - `component`: Allow component names of up to 1024 characters in length. (#10816) - `confmap`: Remove original string representation if invalid. (#10787) ## v0.106.1 ### 🧰 Bug fixes 🧰 - `configauth`: Fix unmarshaling of authentication in HTTP servers. (#10750) ## v0.106.0 ### 🛑 Breaking changes 🛑 - `service`: Update all metrics to include `otelcol_` prefix to ensure consistency across OTLP and Prometheus metrics (#9759) This change is marked as a breaking change as anyone that was using OTLP for metrics will see the new prefix which was not present before. Prometheus generated metrics remain unchanged. - `confighttp`: Delete `ClientConfig.CustomRoundTripper` (#8627) Set (*http.Client).Transport on the *http.Client returned from ToClient to configure this. - `confmap`: When passing configuration for a string field using any provider, use the verbatim string representation as the value. (#10605, #10405) This matches the behavior of `${ENV}` syntax prior to the promotion of the `confmap.unifyEnvVarExpansion` feature gate to beta. It changes the behavior of the `${env:ENV}` syntax with escaped strings. - `component`: Adds restrictions on the character set for component.ID name. (#10673) - `processor/memorylimiter`: The memory limiter processor will no longer account for ballast size. (#10696) If you are already using GOMEMLIMIT instead of the ballast extension this does not affect you. - `extension/memorylimiter`: The memory limiter extension will no longer account for ballast size. (#10696) If you are already using GOMEMLIMIT instead of the ballast extension this does not affect you. - `service`: The service will no longer be able to get a ballast size from the deprecated ballast extension. (#10696) If you are already using GOMEMLIMIT instead of the ballast extension this does not affect you. ### 🚀 New components 🚀 - `client`: Create a new go module `go.opentelemetry.io/collector/client` (#9804) This module contains generic representations of clients connecting to different receivers. ### 💡 Enhancements 💡 - `exporterhelper`: Add data_type attribute to `otelcol_exporter_queue_size` metric to report the type of data being processed. (#9943) - `confighttp`: Add option to include query params in auth context (#4806) - `configgrpc`: gRPC auth errors now return gRPC status code UNAUTHENTICATED (16) (#7646) - `httpprovider, httpsprovider`: Validate URIs in HTTP and HTTPS providers before fetching. (#10468) ### 🧰 Bug fixes 🧰 - `processorhelper`: update units for internal telemetry (#10647) - `confmap`: Increase the amount of recursion and URI expansions allowed in a single line (#10712) - `exporterhelper`: There is no guarantee that after the exporterhelper sends the plog/pmetric/ptrace data downstream that the data won't be mutated in some way. (e.g by the batch_sender) This mutation could result in the proceeding call to req.ItemsCount() to provide inaccurate information to be logged. (#10033) - `exporterhelper`: Update units for internal telemetry (#10648) - `receiverhelper`: Update units for internal telemetry (#10650) - `scraperhelper`: Update units for internal telemetry (#10649) - `service`: Use Command/Version to populate service name/version attributes (#10644) ## v1.12.0/v0.105.0 ### 🛑 Breaking changes 🛑 - `service`: add `service.disableOpenCensusBridge` feature gate which is enabled by default to remove the dependency on OpenCensus (#10414) - `confmap`: Promote `confmap.strictlyTypedInput` feature gate to beta. (#10552) This feature gate changes the following: - Configurations relying on the implicit type casting behaviors listed on [#9532](https://github.com/open-telemetry/opentelemetry-collector/issues/9532) will start to fail. - Configurations using URI expansion (i.e. `field: ${env:ENV}`) for string-typed fields will use the value passed in `ENV` verbatim without intermediate type casting. ### 💡 Enhancements 💡 - `configtls`: Mark module as stable. (#9377) - `confmap`: Remove extra closing parenthesis in sub-config error (#10480) - `configgrpc`: Update the default load balancer strategy to round_robin (#10319) To restore the behavior that was previously the default, set `balancer_name` to `pick_first`. - `cmd/builder`: Add go module info the builder generated code. (#10570) - `otelcol`: Add go module to components subcommand. (#10570) - `confmap`: Add explanation to errors related to `confmap.strictlyTypedInput` feature gate. (#9532) - `confmap`: Allow using `map[string]any` values in string interpolation (#10605) ### 🧰 Bug fixes 🧰 - `builder`: provide context when a module in the config is missing its gomod value (#10474) - `confmap`: Fixes issue where confmap could not escape `$$` when `confmap.unifyEnvVarExpansion` is enabled. (#10560) - `mdatagen`: fix generated comp test for extensions and unused imports in templates (#10477) - `otlpreceiver`: Fixes a bug where the otlp receiver's http response was not properly translating grpc error codes to http status codes. (#10574) - `exporterhelper`: Fix incorrect deduplication of otelcol_exporter_queue_size and otelcol_exporter_queue_capacity metrics if multiple exporters are used. (#10444) - `service/telemetry`: Add ability to set service.name for spans emitted by the Collector (#10489) - `internal/localhostgate`: Correctly log info message when `component.UseLocalHostAsDefaultHost` is enabled (#8510) ## v1.11.0/v0.104.0 This release includes 2 very important breaking changes. 1. The `otlpreceiver` will now use `localhost` by default instead of `0.0.0.0`. This may break the receiver in containerized environments like Kubernetes. If you depend on `0.0.0.0` disable the `component.UseLocalHostAsDefaultHost` feature gate or explicitly set the endpoint to `0.0.0.0`. 2. Expansion of BASH-style environment variables, such as `$FOO` will no longer be supported by default. If you depend on this syntax, disable the `confmap.unifyEnvVarExpansion` feature gate, but know that the feature will be removed in the future in favor of `${env:FOO}`. ### 🛑 Breaking changes 🛑 - `filter`: Remove deprecated `filter.CombinedFilter` (#10348) - `otelcol`: By default, `otelcol.NewCommand` and `otelcol.NewCommandMustSetProvider` will set the `DefaultScheme` to `env`. (#10435) - `expandconverter`: By default expandconverter will now error if it is about to expand `$FOO` syntax. Update configuration to use `${env:FOO}` instead or disable the `confmap.unifyEnvVarExpansion` feature gate. (#10435) - `otlpreceiver`: Switch to `localhost` as the default for all endpoints. (#8510) Disable the `component.UseLocalHostAsDefaultHost` feature gate to temporarily get the previous default. ### 💡 Enhancements 💡 - `confighttp`: Add support for cookies in HTTP clients with `cookies::enabled`. (#10175) The method `confighttp.ToClient` will return a client with a `cookiejar.Jar` which will reuse cookies from server responses in subsequent requests. - `exporter/debug`: In `normal` verbosity, display one line of text for each telemetry record (log, data point, span) (#7806) - `exporter/debug`: Add option `use_internal_logger` (#10226) - `configretry`: Mark module as stable. (#10279) - `debugexporter`: Print Span.TraceState() when present. (#10421) Enables viewing sampling threshold information (as by OTEP 235 samplers). - `processorhelper`: Add "inserted" metrics for processors. (#10353) This includes the following metrics for processors: - `processor_inserted_spans` - `processor_inserted_metric_points` - `processor_inserted_log_records` ### 🧰 Bug fixes 🧰 - `otlpexporter`: Update validation to support both dns:// and dns:/// (#10449) - `service`: Fixed a bug that caused otel-collector to fail to start with ipv6 metrics endpoint service telemetry. (#10011) ## v1.10.0/v0.103.0 ### 🛑 Breaking changes 🛑 - `exporter/debug`: Disable sampling by default (#9921) To restore the behavior that was previously the default, set `sampling_thereafter` to `500`. ### 💡 Enhancements 💡 - `cmd/builder`: Allow setting `otelcol.CollectorSettings.ResolverSettings.DefaultScheme` via the builder's `conf_resolver.default_uri_scheme` configuration option (#10296) - `mdatagen`: add support for optional internal metrics (#10316) - `otelcol/expandconverter`: Add `confmap.unifyEnvVarExpansion` feature gate to allow enabling Collector/Configuration SIG environment variable expansion rules. (#10391) When enabled, this feature gate will: - Disable expansion of BASH-style env vars (`$FOO`) - `${FOO}` will be expanded as if it was `${env:FOO} See https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/rfcs/env-vars.md for more details. - `confmap`: Add `confmap.unifyEnvVarExpansion` feature gate to allow enabling Collector/Configuration SIG environment variable expansion rules. (#10259) When enabled, this feature gate will: - Disable expansion of BASH-style env vars (`$FOO`) - `${FOO}` will be expanded as if it was `${env:FOO} See https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/rfcs/env-vars.md for more details. - `confighttp`: Allow the compression list to be overridden (#10295) Allows Collector administrators to control which compression algorithms to enable for HTTP-based receivers. - `configgrpc`: Revert the zstd compression for gRPC to the third-party library we were using previously. (#10394) We switched back to our compression logic for zstd when a CVE was found on the third-party library we were using. Now that the third-party library has been fixed, we can revert to that one. For end-users, this has no practical effect. The reproducers for the CVE were tested against this patch, confirming we are not reintroducing the bugs. - `confmap`: Adds alpha `confmap.strictlyTypedInput` feature gate that enables strict type checks during configuration resolution (#9532) When enabled, the configuration resolution system will: - Stop doing most kinds of implicit type casting when resolving configuration values - Use the original string representation of configuration values if the ${} syntax is used in inline position - `confighttp`: Use `confighttp.ServerConfig` as part of zpagesextension. See [server configuration](https://github.com/open-telemetry/opentelemetry-collector/blob/main/config/confighttp/README.md#server-configuration) options. (#9368) ### 🧰 Bug fixes 🧰 - `exporterhelper`: Fix potential deadlock in the batch sender (#10315) - `expandconverter`: Fix bug where an warning was logged incorrectly. (#10392) - `exporterhelper`: Fix a bug when the retry and timeout logic was not applied with enabled batching. (#10166) - `exporterhelper`: Fix a bug where an unstarted batch_sender exporter hangs on shutdown (#10306) - `exporterhelper`: Fix small batch due to unfavorable goroutine scheduling in batch sender (#9952) - `confmap`: Fix issue where structs with only yaml tags were not marshaled correctly. (#10282) ## v0.102.1 **This release addresses [GHSA-c74f-6mfw-mm4v](https://github.com/open-telemetry/opentelemetry-collector/security/advisories/GHSA-c74f-6mfw-mm4v) for `configgrpc`.** ### 🧰 Bug fixes 🧰 - `configrpc`: Use own compressors for zstd. Before this change, the zstd compressor we used didn't respect the max message size. This addresses [GHSA-c74f-6mfw-mm4v](https://github.com/open-telemetry/opentelemetry-collector/security/advisories/GHSA-c74f-6mfw-mm4v) for `configgrpc` (#10323) ## v1.9.0/v0.102.0 **This release addresses [GHSA-c74f-6mfw-mm4v](https://github.com/open-telemetry/opentelemetry-collector/security/advisories/GHSA-c74f-6mfw-mm4v) for `confighttp`.** ### 🛑 Breaking changes 🛑 - `envprovider`: Restricts Environment Variable names. Environment variable names must now be ASCII only and start with a letter or an underscore, and can only contain underscores, letters, or numbers. (#9531) - `confighttp`: Apply MaxRequestBodySize to the result of a decompressed body. This addresses [GHSA-c74f-6mfw-mm4v](https://github.com/open-telemetry/opentelemetry-collector/security/advisories/GHSA-c74f-6mfw-mm4v) for `confighttp` (#10289) When using compressed payloads, the Collector would verify only the size of the compressed payload. This change applies the same restriction to the decompressed content. As a security measure, a limit of 20 MiB was added, which makes this a breaking change. For most clients, this shouldn't be a problem, but if you often have payloads that decompress to more than 20 MiB, you might want to either configure your client to send smaller batches (recommended), or increase the limit using the MaxRequestBodySize option. ### 💡 Enhancements 💡 - `mdatagen`: auto-generate utilities to test component telemetry (#19783) - `mdatagen`: support setting an AttributeSet for async instruments (#9674) - `mdatagen`: support using telemetry level in telemetry builder (#10234) This allows components to set the minimum level needed for them to produce telemetry. By default, this is set to configtelemetry.LevelBasic. If the telemetry level is below that minimum level, then the noop meter is used for metrics. - `mdatagen`: add support for bucket boundaries for histograms (#10218) - `releases`: add documentation in how to verify the image signatures using cosign (#9610) ### 🧰 Bug fixes 🧰 - `batchprocessor`: ensure attributes are set on cardinality metadata metric (#9674) - `batchprocessor`: Fixing processor_batch_metadata_cardinality which was broken in v0.101.0 (#10231) - `batchprocessor`: respect telemetry level for all metrics (#10234) - `exporterhelper`: Fix potential deadlocks in BatcherSender shutdown (#10255) ## v1.8.0/v0.101.0 ### 💡 Enhancements 💡 - `mdatagen`: generate documentation for internal telemetry (#10170) - `mdatagen`: add ability to use metadata.yaml to automatically generate instruments for components (#10054) The `telemetry` section in metadata.yaml is used to generate instruments for components to measure telemetry about themselves. - `confmap`: Allow Converters to write logs during startup (#10135) - `otelcol`: Enable logging during configuration resolution (#10056) ### 🧰 Bug fixes 🧰 - `mdatagen`: Run package tests when goleak is skipped (#10125) ## v1.7.0/v0.100.0 ### 🛑 Breaking changes 🛑 - `service`: The `validate` sub-command no longer validates that each pipeline's type is the same as its component types (#10031) ### 💡 Enhancements 💡 - `semconv`: Add support for v1.25.0 semantic convention (#10072) - `builder`: remove the need to go get a module to address ambiguous import paths (#10015) - `pmetric`: Support parsing metric.metadata from OTLP JSON. (#10026) ### 🧰 Bug fixes 🧰 - `exporterhelper`: Fix enabled config option for batch sender (#10076) ## v1.6.0/v0.99.0 ### 🛑 Breaking changes 🛑 - `builder`: Add strict version checking when using the builder. Add the temporary flag `--skip-strict-versioning `for skipping this check. (#9896) Strict version checking will error on major and minor version mismatches between the `otelcol_version` configured and the builder version or versions in the go.mod. This check can be temporarily disabled by using the `--skip-strict-versioning` flag. This flag will be removed in a future minor version. - `telemetry`: Distributed internal metrics across different levels. (#7890) The internal metrics levels are updated along with reported metrics: - The default level is changed from `basic` to `normal`, which can be overridden with `service::telemetry::metrics::level` configuration. - Batch processor metrics are updated to be reported starting from `normal` level: - `processor_batch_batch_send_size` - `processor_batch_metadata_cardinality` - `processor_batch_timeout_trigger_send` - `processor_batch_size_trigger_send` - GRPC/HTTP server and client metrics are updated to be reported starting from `detailed` level: - http.client.* metrics - http.server.* metrics - rpc.server.* metrics - rpc.client.* metrics ### 💡 Enhancements 💡 - `confighttp`: Disable concurrency in zstd compression (#8216) - `cmd/builder`: Allow configuring `confmap.Provider`s in the builder. (#4759) If no providers are specified, the defaults are used. The default providers are: env, file, http, https, and yaml. To configure providers, use the `providers` key in your OCB build manifest with a list of Go modules for your providers. The modules will work the same as other Collector components. - `mdatagen`: enable goleak tests by default via mdatagen (#9959) - `cmd/mdatagen`: support excluding some metrics based on string and regexes in resource_attributes (#9661) - `cmd/mdatagen`: Generate config and factory tests covering their requirements. (#9940) The tests are moved from cmd/builder. - `confmap`: Add `ProviderSettings`, `ConverterSettings`, `ProviderFactories`, and `ConverterFactories` fields to `confmap.ResolverSettings` (#9516) This allows configuring providers and converters, which are instantiated by `NewResolver` using the given factories. ### 🧰 Bug fixes 🧰 - `exporter/otlp`: Allow DNS scheme to be used in endpoint (#4274) - `service`: fix record sampler configuration (#9968) - `service`: ensure the tracer provider is configured via go.opentelemetry.io/contrib/config (#9967) - `otlphttpexporter`: Fixes a bug that was preventing the otlp http exporter from propagating status. (#9892) - `confmap`: Fix decoding negative configuration values into uints (#9060) ## v1.5.0/v0.98.0 ### 🛑 Breaking changes 🛑 - `service`: emit internal collector metrics with _ instead of / with OTLP export (#9774) This is addressing an issue w/ the names of the metrics generated by the Collector for its internal metrics. Note that this change only impacts users that emit telemetry using OTLP, which is currently still in experimental support. The prometheus metrics already replaced `/` with `_` and they will do the same with `_`. ### 💡 Enhancements 💡 - `mdatagen`: Adds unsupported platforms to the README header (#9794) - `confmap`: Clarify the use of embedded structs to make unmarshaling composable (#7101) - `nopexporter`: Promote the nopexporter to beta (#7316) - `nopreceiver`: Promote the nopreceiver to beta (#7316) - `otlpexporter`: Checks for port in the config validation for the otlpexporter (#9505) - `service`: Validate pipeline type against component types (#8007) ### 🧰 Bug fixes 🧰 - `configtls`: Fix issue where `IncludeSystemCACertsPool` was not consistently used between `ServerConfig` and `ClientConfig`. (#9835) - `component`: Fix issue where the `components` command wasn't properly printing the component type. (#9856) - `otelcol`: Fix issue where the `validate` command wasn't properly printing valid component type. (#9866) - `receiver/otlp`: Fix bug where the otlp receiver did not properly respond with a retryable error code when possible for http (#9357) ## v1.4.0/v0.97.0 ### 🛑 Breaking changes 🛑 - `telemetry`: Remove telemetry.useOtelForInternalMetrics stable feature gate (#9752) ### 🚀 New components 🚀 - `exporter/nop`: Add the `nopexporter` to serve as a placeholder exporter in a pipeline (#7316) This is primarily useful for starting the Collector with only extensions enabled or to test Collector pipeline throughput. - `receiver/nop`: Add the `nopreceiver` to serve as a placeholder receiver in a pipeline (#7316) This is primarily useful for starting the Collector with only extensions enabled. ### 💡 Enhancements 💡 - `configtls`: Validates TLS min_version and max_version (#9475) Introduces `Validate()` method in TLSSetting. - `configcompression`: Mark module as Stable. (#9571) - `cmd/mdatagen`: Use go package name for the scope name by default and add an option to provide the scope name in metadata.yaml. (#9693) - `cmd/mdatagen`: Generate the lifecycle tests for components by default. (#9683) It's encouraged to have lifecycle tests for all components enabled, but they can be disabled if needed in metadata.yaml with `skip_lifecycle: true` and `skip_shutdown: true` under `tests` section. - `cmd/mdatagen`: optimize the mdatagen for the case like batchprocessor which use a common struct to implement consumer.Traces, consumer.Metrics, consumer.Logs in the meantime. (#9688) ### 🧰 Bug fixes 🧰 - `exporterhelper`: Fix persistent queue size backup on reads. (#9740) - `processor/batch`: Prevent starting unnecessary goroutines. (#9739) - `otlphttpexporter`: prevent error on empty response body when content type is application/json (#9666) - `confmap`: confmap honors `Unmarshal` methods on config embedded structs. (#6671) - `otelcol`: Respect telemetry configuration when running as a Windows service (#5300) ## v1.3.0/v0.96.0 ### 🛑 Breaking changes 🛑 - `configgrpc`: Remove deprecated `GRPCClientSettings`, `GRPCServerSettings`, and `ServerConfig.ToListenerContext`. (#9616) - `confighttp`: Remove deprecated `HTTPClientSettings`, `NewDefaultHTTPClientSettings`, and `CORSSettings`. (#9625) - `confignet`: Removes deprecated `NetAddr` and `TCPAddr` (#9614) ### 💡 Enhancements 💡 - `configtls`: Add `include_system_ca_certs_pool` to configtls, allowing to load system certs and additional custom certs. (#7774) - `otelcol`: Add `ConfigProviderSettings` to `CollectorSettings` (#4759) This allows passing a custom list of `confmap.Provider`s to `otelcol.NewCommand`. - `pdata`: Update to OTLP v1.1.0 (#9587) Introduces Span and SpanLink flags. - `confmap`: Update mapstructure to use a maintained fork, github.com/go-viper/mapstructure/v2. (#9634) See https://github.com/mitchellh/mapstructure/issues/349 for context. ### 🧰 Bug fixes 🧰 - `configretry`: Allow max_elapsed_time to be set to 0 for indefinite retries (#9641) - `client`: Make `Metadata.Get` thread safe (#9595) ## v1.2.0/v0.95.0 ### 🛑 Breaking changes 🛑 - `all`: scope name for all generated Meter/Tracer funcs now includes full package name (#9494) ### 💡 Enhancements 💡 - `confighttp`: Adds support for Snappy decompression of HTTP requests. (#7632) - `configretry`: Validate `max_elapsed_time`, ensure it is larger than `max_interval` and `initial_interval` respectively. (#9489) - `configopaque`: Mark module as stable (#9167) - `otlphttpexporter`: Add support for json content encoding when exporting telemetry (#6945) - `confmap/converter/expandconverter, confmap/provider/envprovider, confmap/provider/fileprovider, confmap/provider/httpprovider, confmap/provider/httpsprovider, confmap/provider/yamlprovider`: Split confmap.Converter and confmap.Provider implementation packages out of confmap. (#4759, #9460) ## v1.1.0/v0.94.0 ### 🛑 Breaking changes 🛑 - `receiver/otlp`: Update gRPC code from `codes.InvalidArgument` to `codes.Internal` when a permanent error doesn't contain a gRPC status (#9415) ### 🚩 Deprecations 🚩 - `configgrpc`: Deprecate GRPCClientSettings, use ClientConfig instead (#6767) ### 💡 Enhancements 💡 - `mdatagen`: Add a generated test that checks the config struct using `componenttest.CheckConfigStruct` (#9438) - `component`: Add `component.UseLocalHostAsDefaultHost` feature gate that changes default endpoints from 0.0.0.0 to localhost (#8510) The only component in this repository affected by this is the OTLP receiver. - `confighttp`: Add support of Host header (#9395) - `mdatagen`: Remove use of ReportFatalError in generated tests (#9439) ### 🧰 Bug fixes 🧰 - `service`: fix opencensus bridge configuration in periodic readers (#9361) - `otlpreceiver`: Fix goroutine leak when GRPC server is started but HTTP server is unsuccessful (#9165) - `otlpexporter`: PartialSuccess is treated as success, logged as warning. (#9243) ## v0.93.0 ### 🛑 Breaking changes 🛑 - `exporterhelper`: remove deprecated exporterhelper.RetrySettings and exporterhelper.NewDefaultRetrySettings (#9256) - `configopaque`: configopaque.String implements `fmt.Stringer` and `fmt.GoStringer`, outputting [REDACTED] when formatted with the %s, %q or %#v verbs` (#9213) This may break applications that rely on the previous behavior of opaque strings with `fmt.Sprintf` to e.g. build URLs or headers. Explicitly cast the opaque string to a string before using it in `fmt.Sprintf` to restore the previous behavior. ### 🚀 New components 🚀 - `extension/memory_limiter`: Introduce a `memory_limiter` extension which receivers can use to reject incoming requests when collector doesn't have enough memory (#8632) The extension has the same configuration interface and behavior as the existing `memory_limiter` processor, which potentially can be deprecated and removed in the future ### 💡 Enhancements 💡 - `configtls`: add `cipher_suites` to configtls. (#8105) Users can specify a list of cipher suites to pick from. If left blank, a safe default list is used. - `service`: mark `telemetry.useOtelForInternalMetrics` as stable (#816) - `exporters`: Cleanup log messages for export failures (#9219) 1. Ensure an error message is logged every time and only once when data is dropped/rejected due to export failure. 2. Update the wording. Specifically, don't use "dropped" term when an error is reported back to the pipeline. Keep the "dropped" wording for failures happened after the enabled queue. 3. Properly report any error reported by a queue. For example, a persistent storage error must be reported as a storage error, not as "queue overflow". ### 🧰 Bug fixes 🧰 - `configgrpc`: Update dependency to address a potential crash in the grpc instrumentation (#9296) - `otlpreceiver`: Ensure OTLP receiver handles consume errors correctly (#4335) Make sure OTLP receiver returns correct status code and follows the receiver contract (gRPC) - `zpagesextension`: Remove mention of rpcz page from zpages extension (#9328) ## v1.0.1/v0.92.0 ### 🛑 Breaking changes 🛑 - `exporters/sending_queue`: Do not re-enqueue failed batches, rely on the retry_on_failure strategy instead. (#8382) The current re-enqueuing behavior is not obvious and cannot be configured. It takes place only for persistent queue and only if `retry_on_failure::enabled=true` even if `retry_on_failure` is a setting for a different backoff retry strategy. This change removes the re-enqueuing behavior. Consider increasing `retry_on_failure::max_elapsed_time` to reduce chances of data loss or set it to 0 to keep retrying until requests succeed. - `confmap`: Make the option `WithErrorUnused` enabled by default when unmarshaling configuration (#7102) The option `WithErrorUnused` is now enabled by default, and a new option `WithIgnoreUnused` is introduced to ignore errors about unused fields. - `status`: Deprecate `ReportComponentStatus` in favor of `ReportStatus`. This new function does not return an error. (#9148) ### 🚩 Deprecations 🚩 - `connectortest`: Deprecate connectortest.New[Metrics|Logs|Traces]Router in favour of connector.New[Metrics|Logs|Traces]Router (#9095) - `exporterhelper`: Deprecate exporterhelper.RetrySettings in favor of configretry.BackOffConfig (#9091) - `extension/ballast`: Deprecate `memory_ballast` extension. (#8343) Use `GOMEMLIMIT` environment variable instead. - `connector`: Deprecate [Metrics|Logs|Traces]Router in favour of [Metrics|Logs|Traces]RouterAndConsumer (#9095) ### 💡 Enhancements 💡 - `exporterhelper`: Add RetrySettings validation function (#9089) Validate that time.Duration, multiplier values in configretry are non-negative, and randomization_factor is between 0 and 1 - `service`: Enable `telemetry.useOtelForInternalMetrics` by updating the flag to beta (#7454) The metrics generated should be consistent with the metrics generated previously with OpenCensus. Users can disable the behaviour by setting `--feature-gates -telemetry.useOtelForInternalMetrics` at collector start. - `mdatagen`: move component from contrib to core (#9172) - `semconv`: Generated Semantic conventions 1.22.0. (#8686) - `confignet`: Add `dialer_timeout` config option. (#9066) - `processor/memory_limiter`: Update config validation errors (#9059) - Fix names of the config fields that are validated in the error messages - Move the validation from start to the initialization phrase - `exporterhelper`: Add config Validate for TimeoutSettings (#9104) ### 🧰 Bug fixes 🧰 - `memorylimiterprocessor`: Fixed leaking goroutines from memorylimiterprocessor (#9099) - `cmd/otelcorecol`: Fix the code detecting if the collector is running as a service on Windows. (#7350) Removed the `NO_WINDOWS_SERVICE` environment variable given it is not needed anymore. - `otlpexporter`: remove dependency of otlphttpreceiver on otlpexporter (#6454) ## v0.91.0 ### 💡 Enhancements 💡 - `statusreporting`: Automates status reporting upon the completion of component.Start(). (#7682) - `service`: add resource attributes as labels to otel metrics to ensures backwards compatibility with OpenCensus metrics. (#9029) - `semconv`: Generated Semantic conventions 1.21. (#9056) - `config/confighttp`: Exposes http/2 transport settings to enable health check and workaround golang http/2 issue https://github.com/golang/go/issues/59690 (#9022) - `cmd/builder`: running builder version on binaries installed with `go install` will output the version specified at the suffix. (#8770) ### 🧰 Bug fixes 🧰 - `exporterhelper`: fix missed metric aggregations (#9048) This ensures that context cancellation in the exporter doesn't interfere with metric aggregation. The OTel SDK currently returns if there's an error in the context used in `Add`. This means that if there's a cancelled context in an export, the metrics are now recorded. - `service`: Fix bug where MutatesData would not correctly propagate through connectors. (#9053) ## v0.90.1 ### 🧰 Bug fixes 🧰 - `exporterhelper`: Remove noisy log (#9017) ## v1.0.0/v0.90.0 ### 🛑 Breaking changes 🛑 - `service`: To remain backwards compatible w/ the metrics generated today, otel generated metrics will be generated without the `_total` suffix (#7454) - `service`: use WithNamespace instead of WrapRegistererWithPrefix (#8988) Using this functionality in the otel prom exporter fixes a bug where the target_info was prefixed as otelcol_target_info previously. ### 💡 Enhancements 💡 - `exporter/debug`: Change default `verbosity` from `normal` to `basic` (#8844) This change has currently no effect, as `basic` and `normal` verbosity share the same behavior. This might change in the future though, with the `normal` verbosity being more verbose than it currently is (see https://github.com/open-telemetry/opentelemetry-collector/issues/7806). This is why we are changing the default to `basic`, which is expected to stay at the current level of verbosity (one line per batch). - `exporterhelper`: Fix shutdown logic in persistent queue to not require consumers to be closed first (#8899) - `confighttp`: Support proxy configuration field in all exporters that support confighttp (#5761) ### 🧰 Bug fixes 🧰 - `exporterhelper`: Fix invalid write index updates in the persistent queue (#8115) ## v1.0.0-rcv0018/v0.89.0 ### 💡 Enhancements 💡 - `builder`: remove replace statement in builder template (#8763) - `service/extensions`: Allow extensions to declare dependencies on other extensions and guarantee start/stop/notification order accordingly. (#8732) - `exporterhelper`: Log export errors when retry is not used by the component. (#8791) - `cmd/builder`: Add --verbose flag to log `go` subcommands output that are ran as part of a build (#8715) - `exporterhelper`: Remove internal goroutine loop for persistent queue (#8868) - `exporterhelper`: Simplify usage of storage client, avoid unnecessary allocations (#8830) - `exporterhelper`: Simplify logic in boundedMemoryQueue, use channels len/cap (#8829) ### 🧰 Bug fixes 🧰 - `exporterhelper`: fix bug with queue size and capacity metrics (#8682) - `obsreporttest`: split handler for otel vs oc test path in TestTelemetry (#8758) - `builder`: Fix featuregate late initialization (#4967) - `service`: Fix connector logger zap kind key (#8878) ## v1.0.0-rcv0017/v0.88.0 ### 💡 Enhancements 💡 - `fanoutconsumer`: Enable runtime assertions to catch incorrect pdata mutations in the components claiming as non-mutating pdata. (#6794) This change enables the runtime assertions to catch unintentional pdata mutations in components that are claimed as non-mutating pdata. Without these assertions, runtime errors may still occur, but thrown by unrelated components, making it very difficult to troubleshoot. ### 🧰 Bug fixes 🧰 - `exporterhelper`: make enqueue failures available for otel metrics (#8673) - `exporterhelper`: Fix nil pointer dereference when stopping persistent queue after a start encountered an error (#8718) - `cmd/builder`: Fix ocb ignoring `otelcol_version` when set to v0.86.0 or later (#8692) ## v1.0.0-rcv0016/v0.87.0 ### 💡 Enhancements 💡 - `service/telemetry exporter/exporterhelper`: Enable sampling logging by default and apply it to all components. (#8134) The sampled logger configuration can be disabled easily by setting the `service::telemetry::logs::sampling::enabled` to `false`. - `core`: Adds the ability for components to report status and for extensions to subscribe to status events by implementing an optional StatusWatcher interface. (#7682) ### 🧰 Bug fixes 🧰 - `telemetry`: remove workaround to ignore errors when an instrument includes a `/` (#8346) ## v1.0.0-rcv0015/v0.86.0 ### 🚩 Deprecations 🚩 - `loggingexporter`: Mark the logging exporter as deprecated, in favour of debug exporter (#7769) ### 🚀 New components 🚀 - `debugexporter`: Add debug exporter, which replaces the logging exporter (#7769) ### 💡 Enhancements 💡 - `featuregate`: List valid feature gates when failing to load invalid gate (#8505) - `supported platforms`: Add `linux/s390x` architecture to cross build tests in CI (#8213) ### 🧰 Bug fixes 🧰 - `builder`: fix setting `dist.*` keys from env (#8239) - `configtls`: fix incorrect use of fsnotify (#8438) ## v0.85.0 ### 💡 Enhancements 💡 - `components command`: The "components" command now lists the component's stability levels. (#8289) Note that the format of this output is NOT stable and can change between versions. - `confighttp`: Add option to disable HTTP keep-alives (#8260) ### 🧰 Bug fixes 🧰 - `confmap`: fix bugs of unmarshalling slice values (#4001) - `exporterhelper`: Stop logging error messages suggesting user to enable `retry_on_failure` or `sending_queue` when they are not available. (#8369) ## v0.84.0 ### 💡 Enhancements 💡 - `loggingexporter`: Adds exemplars logging to the logging exporter when `detailed` verbosity level is set. (#7912) - `configgrpc`: Allow any registered gRPC load balancer name to be used. (#8262) - `service`: add OTLP export for internal traces (#8106) - `configgrpc`: Add support for :authority pseudo-header in grpc client (#8228) ### 🧰 Bug fixes 🧰 - `otlphttpexporter`: Fix the handling of the HTTP response to ignore responses not encoded as protobuf (#8263) ## v0.83.0 ### 💡 Enhancements 💡 - `extension`: Add optional `ConfigWatcher` interface (#6596) Extensions implementing this interface will be notified of the Collector's effective config. - `otelcol`: Add optional `ConfmapProvider` interface for Config Providers (#6596) This allows providing the Collector's configuration as a marshaled confmap.Conf object from a ConfigProvider - `service`: Add `CollectorConf` field to `service.Settings` (#6596) This field is intended to be used by the Collector to pass its effective configuration to the service. ## v1.0.0-rcv0014/v0.82.0 ### 🛑 Breaking changes 🛑 - `service`: Enable configuration of collector telemetry through prometheus reader (#7641) These options are still experimental. To enable them, users must enable both `telemetry.useOtelForInternalMetrics` and `telemetry.useOtelWithSDKConfigurationForInternalTelemetry` feature gates. This change updates `metric_readers` to `readers` to align with the configuration working group. - `service`: Remove experimental `metric_readers.args` and `metric_reader.type` config options. (#7641) These options were experimental and did not have any effect on the configuration of the collector's telemetry. The change aligns the configuration with the latest iteration of the configuration json schema, which may still change in the future. ### 💡 Enhancements 💡 - `service`: Add support for exporting internal metrics to the console (#7641) Internal collector metrics can now be exported to the console using the otel-go stdout exporter. - `service`: Add support for `interval` and `timeout` configuration in periodic reader (#7641) - `service`: Add support for span processor configuration for internal traces (#8106) These options are still experimental. To enable them, users must enable both `telemetry.useOtelForInternalMetrics` and `telemetry.useOtelWithSDKConfigurationForInternalTelemetry` feature gates. - `service`: Add support for OTLP export for internal metrics (#7641) Internal collector metrics can now be exported via OTLP using the otel-go otlpgrpc and otlphttp exporters. - `scraperhelper`: Adding optional timeout field to scrapers (#7951) - `otlpreceiver`: Add http url paths per signal config options to otlpreceiver (#7511) - `otlphttpexporter`: Add support for trailing slash in endpoint URL (#8084) URLs like `http://localhost:4318/` will now be treated as if they were `http://localhost:4318` ### 🧰 Bug fixes 🧰 - `connector`: Fix connector validation (#7892) Validation of connectors was too aggressive such that a connector that was used in any combination of unsupported roles would fail. Instead, validation should pass as long as each use of the connector has a supported corresponding use. ## v0.81.0 ### 🛑 Breaking changes 🛑 - `service`: Remove 'service.connectors' featuregate (#7952) ### 💡 Enhancements 💡 - `HTTPServerSettings`: Add zstd support to HTTPServerSettings (#7927) This adds ability to decompress zstd-compressed HTTP requests to| all receivers that use HTTPServerSettings. - `cmd/builder`: Add "--skip-generate" option to make builder skip source generation (#7541) - `confighttp`: Add support for additional content decoders via `WithDecoder` server option (#7977) - `connectortest`: Add helpers to aid the construction of `connector.TracesRouter`, `connector.MetricsRouter`, and `connector.LogsRouter` instances to `connectortest`. (#7672) - `confighttp`: Add `response_headers` configuration option on HTTPServerSettings. It allows for additional headers to be attached to each HTTP response sent to the client (#7328) - `otlpreceiver, otlphttpexporter, otlpexporter, configgrpc`: Upgrade github.com/mostynb/go-grpc-compression and switch to nonclobbering imports (#7920) consumers of this library should not have their grpc codecs overridden - `otlphttpexporter`: Treat partial success responses as errors (#6686) ### 🧰 Bug fixes 🧰 - `HTTPServerSettings`: Ensure requests with unsupported Content-Encoding return HTTP 400 Bad Request (#7927) ## v1.0.0-rcv0013/v0.80.0 ### 🚩 Deprecations 🚩 - `service`: Deprecate service.PipelineConfig in favor of pipelines.Config. (#7854) ### 💡 Enhancements 💡 - `service`: Added dry run flag to validate config file without running collector. (#4671) - `configtls`: Allow TLS Settings to be provided in memory in addition to filepath. (#7313) - `connector`: Updates the way connector nodes are built to always pass a fanoutconsumer to their factory functions. (#7672, #7673) - `otlp`: update otlp protos to v0.20.0 (#7839) - `configauth`: Split config/configauth into its own module (#7895) - `configgrpc, confighttp, config/internal`: Split confighttp, configgrpc, and config/internal into separate modules (#7895) - `confignet`: Split config/confignet into its own module (#7895) - `configopaque`: Split config/configopaque into its own module (#7895) - `configtelemetry`: Split config/configtelemetry into its own module (#7895) - `configtls`: Split config/configtls into its own module (#7895) - `configcompression`: Split config/configcompression into its own module (#7895) - `extension`: Splitting `extension/auth` into separate module (#7054) - `connector`: Split connector into its own module (#7895) - `extension`: split extension module into its own module (#7306) - `processor`: Split the processor into its own go module (#7307) - `confighttp`: Avoid re-creating the compressors for every request. (#7859) - `otlpexporter`: Treat partial success responses as errors (#6686) - `service/pipelines`: Add pipelines.Config to remove duplicate of the pipelines configuration (#7854) ## v0.79.0 ### 🚩 Deprecations 🚩 - `component`: Deprecate Host.GetExporters function (#7370) ### 💡 Enhancements 💡 - `otelcol`: Add connectors to output of the `components` command (#7809) - `scraperhelper`: Will start calling scrapers on component start. (#7635) The change allows scrapes to perform their initial scrape on component start and provide an initial delay. This means that scrapes will be delayed by `initial_delay` before first scrape and then run on `collection_interval` for each consecutive interval. - `batchprocessor`: Change multiBatcher to use sync.Map, avoid global lock on fast path (#7714) ### 🧰 Bug fixes 🧰 - `connectors`: When replicating data to connectors, consider whether the next pipeline will mutate data (#7776) ## v0.78.2 ### 🧰 Bug fixes 🧰 - `batchprocessor`: Fix return error for batch processor when consuming Metrics and Logs (#7711) ## v0.78.1 ### 🧰 Bug fixes 🧰 - `batchprocessor`: Fix start/stop logic for batch processor (#7708) ## v1.0.0-rcv0012/v0.78.0 ### 💡 Enhancements 💡 - `batchprocessor`: Add support for batching by metadata keys. (#4544) - `service`: Add feature gate `telemetry.useOtelWithSDKConfigurationForInternalTelemetry` that will add support for configuring the export of internal telemetry to additional destinations in future releases (#7641) - `forwardconnector`: Promote to beta (#7579) - `featuregate`: Promote `featuregate` to the stable module-set (#7693) ### 🧰 Bug fixes 🧰 - `featuregate`: Fix issue where `StageDeprecated` was not usable (#7586) - `exporterhelper`: Fix persistent storage behaviour with no available space on device (#7198) ## v0.77.0 ### 🛑 Breaking changes 🛑 - `exporterhelper`: Reduce the default queue size to 1000 from 5000 (#7359) Affects any exporter which enables the queue by default and doesn't set its own default size. For example: otlphttp. - `featuregate`: Remove deprecated `RemovalVersion` and `WithRegisterRemovalVersion` functions. (#7587) ### 💡 Enhancements 💡 - `service`: Adds ResourceAttributes map to telemetry settings and thus CreateSettings. (#6599) - `service`: Allows users to disable high cardinality OTLP attributes behind a feature flag. (#7517) - `featuregate`: Finalize purpose of `toVersion`. Allow stable gates to be explicitly set to true, but produce a warning log. (#7626) ### 🧰 Bug fixes 🧰 - `config/confighttp`: Ensure Auth RoundTripper follows compression/header changes (#7574) - `otlpreceiver`: do not reject requests having 'content-type' header with optional parameters (#7452) ## v1.0.0-rcv0011/v0.76.1 ### 🛑 Breaking changes 🛑 - `confmap`: Using an Invalid Scheme in a URI will throw an error. (#7504) ### 🚩 Deprecations 🚩 - `featuregate`: Deprecate Gate.RemovalVersion and WithRegisterRemovalVersion in favor of ToVersion. (#7043) ### 💡 Enhancements 💡 - `batchprocessor`: Support zero timeout. (#7508) This allows the batchprocessor to limit request sizes without introducing delay in a pipeline, to act only as a splitter. - `service`: use the otel opencensus bridge when telemetry.useOtelForInternalMetrics is enabled (#7483) - `connector`: Mark 'service.connectors' featuregate as stable (#2336) - `featuregate`: Add a new Deprecated stage for feature gates, when features are abandoned. (#7043) - `loggingexporter`: Show more counters in not detailed verbosity (#7461) The logging exporter now shows more counters when the verbosity is not detailed. The following numbers are added: - Number of resource logs - Number of resource spans - Number of resource metrics - Number of data points - `configtls`: Reload mTLS ClientCA certificates on file change (#6524) - `confmap`: Add support for nested URIs. (#7117) - `featuregate`: Add concept of gate lifetime, [fromVersion, toVersion]. (#7043) ### 🧰 Bug fixes 🧰 - `obsreport`: fix issue where send_failed_requests counter was reporting an incorrect value. (#7456) ## v1.0.0-rc9/v0.75.0 ### 🛑 Breaking changes 🛑 - `featuregate`: Remove deprecated featuregate.FlagValue (#7401) ### 💡 Enhancements 💡 - `provider`: Added userfriendly error on incorrect type. (#7399) ### 🧰 Bug fixes 🧰 - `loggingexporter`: Fix display of bucket boundaries of exponential histograms to correctly reflect inclusive/exclusive bounds. (#7445) - `exporterhelper`: Fix a deadlock in persistent queue initialization (#7400) ## v1.0.0-rc8/v0.74.0 ### 🛑 Breaking changes 🛑 - `consumererror`: Remove deprecated funcs in consumererror (#7357) ### 🚩 Deprecations 🚩 - `featuregate`: Deprecate `FlagValue` in favor of `NewFlag`. (#7042) ### 💡 Enhancements 💡 - `service`: Enable connectors by default by moving service.connectors featuregate to beta (#7369) ## v0.73.0/v1.0.0-rc7 ### 🛑 Breaking changes 🛑 - `consumererror`: Remove `Get` prefix from methods returning failed signal data (#7048) - `service`: Feature gate `service.graph` is now stable and cannot be disabled. It will be removed in the next version. (#2336) ### 💡 Enhancements 💡 - `exporter`: split exporter into its own module (#7239) - `receiver`: split receiver into its own module (#7174) - `connectors`: Provide connectors with a mechanism to route data to specific pipelines (#7152) - `confmap`: Mark `confmap.expandEnabled` as stable (#7323) ## v1.0.0-rc6/v0.72.0 ### 🛑 Breaking changes 🛑 - `all`: Remove go 1.18 support, bump minimum to go 1.19 and add testing for 1.20 (#7151) - `pdata`: Remove deprecated `[Metrics|Traces|Logs]MoveTo` methods. (#7165) - `featuregate`: Remove deprecated funcs in featuregate. (#7173) ### 💡 Enhancements 💡 - `semconv`: Generated Semantic conventions 1.17 that now contains the `event` type. (#7170) - `semconv`: Generated Semantic conventions 1.18. (#7168) ### 🧰 Bug fixes 🧰 - `memorylimiterprocessor`: Fix incorrect parsing of cgroups when running Collector with host mount (#6826) - `confmap`: Clear list of old already closed closers. (#7215) ## v1.0.0-rc5/v0.71.0 ### 🛑 Breaking changes 🛑 - `pdata`: Add private method to GrpcServer interface, disallow direct implementation (#6966) - `featuregate`: Remove deprecated GetRegistry (#7011) - `pcommon`: Remove deprecated Map.Sort (#6688) ### 🚩 Deprecations 🚩 - `featuregate`: Deprecate Registry.List in favor of Registry.VisitAll. (#7041) - `featuregate`: Deprecate Apply in favor of Set (#7018) - `pdata`: Deprecate [Metrics|Logs|Traces].MoveTo methods. (#7091) - `featuregate`: Deprecate RegistryOption in favor of RegisterOption (#7012) - `featuregate`: Deprecate featuregate.Registry.[IsEnabled, RegisterID, MustRegister] (#6998) ### 🚀 New components 🚀 - `httpsprovider`: Add the httpsprovider. This component allows the collector to fetch configurations from web servers using the HTTPS protocol. (#6683) ### 💡 Enhancements 💡 - `exporter`: Allow configuration of fields, `RandomizationFactor` and `Multiplier`, for exponential backoff algorithm when retry on failure is enabled (#6610) - `connectors`: Add "connectors", a new type of pipeline component (#2336) - Connectors connect pipelines by acting as an exporter in one or more pipelines and simultaneously as a receiver of corresponding data in one or more other pipelines. For example: - The `forward` connector can export data to another pipeline of the same type. This allows you to merge data from multiple pipelines onto a common pipeline. Or, you can replicate data onto multiple pipelines so that it may be processed in different ways and/or exported to different backends. - The `count` connector can count data of any type. Regardless of the type of data that is counted, it emits counts as metrics onto a metrics pipeline. - Connectors are currently disabled by default but can be enabled with the `service.connectors` feature gate. - See the [connectors README](https://github.com/open-telemetry/opentelemetry-collector/blob/main/connector/README.md) for more details on how to use connectors. - `service`: Enable new pipelines implementation using graphs. Controlled by the `service.graph` featuregate. (#2336) - `builder`: added ldflags command option (#6940) - `proctelemetry`: Instrument `proctelemetry.ProcessMetrics` metrics with otel-go (#6886) - `capabilityconsumer`: If the consumer has already the desired capability, don't wrap (#7116) - `confmap`: Add support to resolve embedded uris inside a string, concatenate results. (#6932) ### 🧰 Bug fixes 🧰 - `confmap`: Fix bug in confmap validation that allowed the usage of case-insensitive keys in the configurations, despite them failing silently. (#6876) - `logging`: Fix the attribute key used to identify the receiver data type in logging configuration (#7033) ## v1.0.0-RC4/v0.70.0 ### 🛑 Breaking changes 🛑 - `pdata`: Start enforcing grpc server implementation to embed UnimplementedGRPCServer, disallow client implementation (#6966) - `config/configgrpc`: Change configgrpc.GRPCClientSettings.Headers type to map[string]configopaque.String (#6852) Use `configopaque.String(str)` and `string(opaque)` to turn a string opaque/clear. - `pdata`: Remove deprecated pcommon.Value.Equal (#6860) ### 🚩 Deprecations 🚩 - `pdata`: Deprecate pcommon.Map.Sort(). (#6688) - `featuregate`: Deprecate GetRegistry in favor of GlobalRegistry (#6979) ### 💡 Enhancements 💡 - `builder`: Add remote debug option for otel-collector to builder (#6149) - `connector`: Add Builder (#6867) - `cmd/builder`: Add support for connector configurations (#6789) - `exporter/otlphttp`: Retry only on status code 429/502/503/504 (#6845) - `featuregate`: Reduce contention in featuregate by using sync.Map instead of mutex. (#6980) ### 🧰 Bug fixes 🧰 - `loggingexporter`: Fix undefined symbol errors on building otelcorecol for other platforms than darwin, linux, windows. (#6924) - `otlpexporter`: Fix a dataloss bug in persistent storage when collector shuts down or restarts (#6771) ## v0.69.1 ### 🧰 Bug fixes 🧰 - `various modules`: Fix issue where some collector modules imported previous version of other modules (#6929) ## v1.0.0-RC3/v0.69.0 ### 🛑 Breaking changes 🛑 - `component`: Remove deprecated Exporter types (#6880) - `component`: Remove deprecated Extension types (#6865) - `component`: Remove deprecated ProcessorFactoryOptions (#6881) - `component`: Remove deprecated Receiver types (#6882) - `componenttest`: Remove deprecated funcs from componenttest (#6836) - `batchprocessor`: Remove deprecated batchprocessor.MetricViews and batchprocessor.OtelMetricViews (#6861) - `component`: Remove deprecated component.[Factories|MakeProcessorFactoryMap] and componenttest.NewNopFactories (#6835) - `config`: Remove deprecated config.*Settings (#6837) - `obsreporttest`: Remove deprecated obsreporttest.SetupTelemetryWithID (#6861) - `component`: Remove deprecated component [Traces|Metrics|Logs]Processor and ProcessorFactory (#6884) - `service`: Remove deprecated service service.ConfigService and service.ConfigServicePipeline (#6859) ### 💡 Enhancements 💡 - `connector`: Add MakeFactoryMap (#6889) - `semconv`: Add semantic conventions for specification v1.16.0 (#6714) ### 🧰 Bug fixes 🧰 - `config`: use [REDACTED] when marshaling to text a configopaque.String, instead of disclosing secret length. (#6868) ## v1.0.0-RC2/v0.68.0 ### 🛑 Breaking changes 🛑 - `componenttest`: Move NopFactories to otelcoltest (#6792) - `config/confighttp`: Change confighttp.HTTPClientSettings.Headers type to map[string]configopaque.String (#5653) - `config`: Remove deprecated `component.Config.[ID|SetIDName]`. (#4714) - `configauth`: Remove deprecated funcs/types from `configauth` (#6719) - `component`: Remove deprecated funcs/types from component package (#6769) - `component.[Exporter|Processor|Receiver|Extension]Config` - `component.Unmarshal[Exporter|Processor|Receiver|Extension]Config` - `component.[Exporter|Processor|Receiver|Extension]CreateDefaultConfigFunc` - `component.[Exporter|Receiver|Extension]FactoryOption` - `component.New[Exporter|Receiver|Extension]Factory` - `component.With[Traces|Metrics|Logs][Exporter|Receiver]` - `component.Create[Traces|Metrics|Logs][Exporter|Receiver]Func` - `component.CreateExtensionFunc` - `componenttest`: Remove deprecated componenttest.NewNop*CreateSettings (#6761) - `service`: Remove deprecated `service.[Collector|New|CollectorSettings|ConfigProvider]` (#5564) - `service`: Remove deprecated funcs `service.NewCommand` and `service.NewSvcHandler`. (#5564) - `obsreporttest`: Remove deprecate obsreporttest.Check* (#6720) - `service`: Remove deprecated `service.Config`. (#6774) - `servicetest`: Remove deprecated `servicetest` package. (#5564) ### 🚩 Deprecations 🚩 - `service`: Deprecate `service.ConfigService` in favor of `service.Config` and `service.ConfigServicePipeline` in favor of `service.PipelineConfig`. (#6787) - `pdata`: Deprecate `pcommon.Value.Equal` method (#6811) - `component`: Deprecate `Processor` related structs and functions in favor of `processor` package (#6709) - `component`: Deprecate component.Factories in favor of otelcol.Factories (#6723) - `config`: Deprecate `config.[Extension|Exporter|Connector|Receiver|Processor]Settings`. (#6718) - `batchprocessor`: Deprecate metric views funcs, for OC and Otel. (#6730) - `obsreporttest`: Deprecate obsreporttest.SetupTelemetryWithID in favor of obsreporttest.SetupTelemetry (#6720) ### 🚀 New components 🚀 - `forwardconnector`: Add forward connector (#6763) ### 💡 Enhancements 💡 - `components`: Add [receiver|processor|exporter|extension].Builder to help with creating components form a set of configs and factories (#6803) - `configunmarshaler`: Consolidate package into generic implementation (#6801) - `service`: Shutdown internal telemetry with the Service (every time config changes). (#5564) ### 🧰 Bug fixes 🧰 - `configgrpc`: Fix todo to add MeterProvider to grpc instrumentation (#4030) - `otlpreceiver`: Fix otlpreceiver transport metrics attribute (#6695) ## v1.0.0-RC1/v0.67.0 We are excited to announce that the `pdata` module is now available as a v1.0.0 release candidate. While breaking changes may still happen in this module before v1.0.0, we believe it is ready for final assessment and validation and hope to make a v1.0.0 release soon. ### 🛑 Breaking changes 🛑 - `overwritepropertiesconverter`: Remove deprecated package `overwritepropertiesconverter` (#6656) - `pdata`: Change [...Slice|Map].Sort methods to not return any value (#6660) - `featuregate`: remove deprecated functions (#6594) - `featuregate.GetID()` - `featuregate.GetDescription()` - `obsreport`: remove deprecated functions. (#6595) - `obsreport.MustNewExporter` - `obsreport.MustNewProcessor` - `obsreport.MustNewReceiver` - `obsreport.MustNewScraper` - `service`: Remove deprecated `service.State` enum values. (#6605) - `component`: Remove deprecated func/types from component (#6606) - `config`: Remove deprecated config.Pipelines and config.Pipeline (#6664) - `ballastextension`: Remove deprecated `ballastextension.MemoryBallast` type (#6628) - `component`: Remove `Validate()` from component.*Config interfaces and make it optional interface (#6544) - `confmap`: Splitting confmap into its own module (#6185) The import path for the confmap module can now be access directly: - `go.opentelemetry.io/collector/confmap` ### 🚩 Deprecations 🚩 - `service`: Deprecate service.[Collector|NewSvcHandler|CollectorSettings|State|NewCommand] in favor of otelcol package" (#6608) - Deprecate `service.Config` in favor of `otelcol.Config`. - Deprecate `service.ConfigProvider` in favor of `otelcol.ConfigProvider`. - Deprecate `service.NewConfigProvider` in favor of `otelcol.NewConfigProvider`. - Deprecate `service.CollectorSettings` in favor of `otelcol.CollectorSettings`. - Deprecate `service.Collector` in favor of `otelcol.Collector`. - Deprecate `service.New` in favor of `otelcol.NewCollector`. - Deprecate `service.State` in favor of `otelcol.State`. - Deprecate `service.NewSvcHandler` in favor of `otelcol.NewSvcHandler`. - Deprecate `service.NewCommand` in favor of `otelcol.NewCommand`. - `obsreporttest`: Deprecate obsreporttest.Check* in favor of TestTelemetry.Check (#6678) - `component`: Deprecate Exporter related types/funcs from component package in favor of exporter package. (#6578) - `component.ExporterCreateSettings` -> `exporter.CreateSettings` - `component.CreateTracesExporterFunc` -> `exporter.CreateTracesFunc` - `component.CreateMetricsExporterFunc` -> `exporter.CreateMetricsFunc` - `component.CreateLogsExporterFunc` -> `exporter.CreateLogsFunc` - `component.ExporterFactory` -> `exporter.Factory` - `component.NewExporterFactory` -> `exporter.NewFactory` - `component.MakeExporterFactoryMap` -> `exporter.MakeFactoryMap` - `componenttest.NewNopExporterCreateSettings` -> `exportertest.NewNopCreateSettings` - `componenttest.NewNopExporterFactory` -> `exportertest.NewNopFactory` - `component`: Change Config to be opaque for otel collector core. (#4714) - Deprecate `component.Config.ID()` in favor of `component.[*]CreateSettings.ID`. - Deprecate `component.Config.SetIDName()`, no replacement needed since ID in settings is public member. - Deprecate `obsreporttest.SetupTelemetry` in favor of `obsreporttest.SetupTelemetryWithID`. - `component`: Deprecate `component.Unmarshal[*]Config` in favor of `component.UnmarshalConfig` (#6613) - `component`: Deprecate Extension related types/funcs from component package in favor of extension package. (#6578) - `component.Extension` -> `extension.Extension` - `component.PipelineWatcher` -> extension.PipelineWatcher - `component.ExtensionCreateSettings` -> `extension.CreateSettings` - `component.CreateExtensionFunc` -> `extension.CreateFunc` - `component.ExtensionFactory` -> `extension.Factory` - `component.NewExtensionFactory` -> `extension.NewFactory` - `component.MakeExtensionFactoryMap` -> `extension.MakeFactoryMap` - `componenttest.NewNopExtensionCreateSettings` -> `extensiontest.NewNopCreateSettings` - `componenttest.NewNopExtensionFactory` -> `extensiontest.NewNopFactory` - `component`: Deprecate `Receiver` related structs and functions in favor of `receiver` package (#6687) - `component`: Deprecate `component.[Exporter|Extension|Processor|Receiver]Config` in favor of `component.Config` (#6578) - `pdata`: Remove deprecated funcs `pdata.[Span|Trace]ID.HexString` (#6627) ### 💡 Enhancements 💡 - `config/configopaque`: Add new `configopaque.String` type alias for opaque strings. (#5653) - `service`: Added components sub command which outputs components in collector distribution. (#4671) - `component`: Define new component type 'connectors' (#6577) - `connector`: Add connector factory (#6611) - `connector`: Add connector types (TracesConnector, MetricsConnector, LogsConnector) (#6689) - `connectortest`: Add connector/connectortest package (#6711) - `component`: Add recursive validation check for configs (#4584) - `service`: Improve config error messages, split Validate functionality (#6665) - `extension/authextension`: Define new authextension package and use new package in collector repo (#6467) - configauth.ClientAuthenticator -> auth.Client - configauth.NewClientAuthenticator -> auth.NewClient - configauth.ClientOption -> auth.ClientOption - configauth.WithClientStart -> auth.WithClientStart - configauth.WithClientShutdown -> auth.WithClientShutdown - configauth.WithClientRoundTripper -> auth.WithClientRoundTripper - configauth.WithPerRPCCredentials -> auth.WithClientPerRPCCredentials - configauth.ServerAuthenticator -> auth.Server - configauth.NewServerAuthenticator -> auth.NewServer - configauth.Option -> auth.ServerOption - configauth.AuthenticateFunc -> auth.ServerAuthenticateFunc - configauth.WithAuthenticate -> auth.WithServerAuthenticate - configauth.WithStart -> auth.WithServerStart - configauth.WithShutdown -> auth.WithServerShutdown - `obsreport`: Instrument `obsreport.Processor` metrics with otel-go (#6607) - `pdata`: Add ability to clear optional fields (#6474) ### 🧰 Bug fixes 🧰 - `otlpexporter`: Fix nil panic from otlp exporter in case of errors during Start. (#6633) - `service`: Stop notification for signals before shutdown, increase channel size. (#6522) - `confmap`: Fix support for concatenating envvars with colon (#6580) - `otlpexporter`: Fix a bug that exporter persistent queue is sending duplicate data after restarting. (#6692) ## v0.65.0 ### 🛑 Breaking changes 🛑 - `featuregate`: Capitalize `featuregate.Stage` string values, remove Stage prefix. (#6490) - `configtelemetry`: Update values returned by `Level.String` and `Level.MarshalText` method. (#6490) - All returned strings are capitalized. - "" is returned for integers that are out of Level enum range. - It also affects `Level.Marshal` output, but it's not a problem because `Unmarshal` method accepts strings in all cases, e.g. "normal", "Normal" and "NORMAL". - `featuregate`: Make impossible to implement RegistryOption outside `featuregate` package (#6532) - `service/telemetry`: Remove unit suffixes from metrics exported by the otel-go prometheus exporter. (#6403) - `obsreport`: `obsreport.New[Receiver|Scraper|Processor|Exporter]` returns error now (#6458) - `configgrpc`: Remove deprecated funcs in `configgrpc`. (#6529) - `configgrpc.GRPCClientSettings.ToDialOptions` - `configgrpc.GRPCServerSettings.ToServerOption` - `config/configtest`: Remove deprecated `configtest` package. (#6542) - `config`: Remove deprecated types and funcs from config. Use `component` package. (#6511) - config.ComponentID - config.Type - config.DataType - config.Receiver - config.UnmarshalReceiver - config.Processor - config.UnmarshalProcessor - config.Exporter - config.UnmarshalExporter - config.Extension - config.UnmarshalExtension - `featuregate`: Remove deprecated funcs and struct members from `featuregate` package (#6523) - featuregate.Gate.ID - featuregate.Gate.Description - featuregate.Gate.Enabled - featuregate.Registry.Register - featuregate.Registry.MustRegister - `experimental`: Remove experimental configsource code. (#6558) - `component`: Update values returned by `StabilityLevel.String` method. (#6490) - All returned strings are capitalized. - "Undefined" is returned only for `StabilityLevelUndefined`. - "" is returned for integers that are out of StabilityLevel enum range. ### 🚩 Deprecations 🚩 - `pdata`: Deprecate `pcommon.[Span|Trace]ID.HexString` methods. Call `hex.EncodeToString` explicitly instead. (#6514) - `obsreport`: deprecate `obsreport.MustNew[Receiver|Scraper|Processor|Exporter]` in favor of `obsreport.New[Receiver|Scraper|Processor|Exporter]` (#6458) - Deprecate `obsreport.MustNewReceiver()` in favor of `obsreport.NewReceiver()` - Deprecate `obsreport.MustNewScraper()` in favor of `obsreport.NewScraper()` - Deprecate `obsreport.MustNewProcessor()` in favor of `obsreport.NewProcessor()` - Deprecate `obsreport.MustNewExporter()` in favor of `obsreport.NewExporter()` - `component`: Deprecate `component.Receiver`, `component.Processor`, and `component.Exporter`. (#6553) - `featuregate`: Deprecate Get prefix funcs for `featuregate.Gate` (#6528) `featuregate.Gate.GetID` -> `featuregate.Gate.ID` `featuregate.Gate.GetDescription` -> `featuregate.Gate.Description` - `component`: Deprecate `component.Config.Validate` in favor of `component.ValidateConfig` (#6572) - `component`: Deprecate `StabilityLevelInDevelopment` enum const in favor of `StabilityLevelDevelopment`. (#6561) Also rename all mentions of "In development" stability level to "Development". - `service`: Deprecate `service.[Starting|Running|Closing|Closed]` in favor of `service.State[Starting|Running|Closing|Closed]` (#6492) ### 💡 Enhancements 💡 - `component`: `component.Extension` is temporarily set to be an alias of `component.Component` which will be reverted once it's moved to the `extension` package. Change your `component.Host.GetExtensions()` implementation to return `map[ID]component.Component` instead of `map[ID]component.Extension` (#6553) - `pdata`: Return error from `pcommon.[Value|Map|Slice].FromRaw` when unsupported type. (#6579) - `batchprocessor`: instrument the `batch` processor with OpenTelemetry Go SDK (#6423) - `obsreport`: Instrument `obsreport.Scraper` metrics with otel-go (#6460) - `service/collector`: Support SIGHUP configuration reloading (#5966) - `component`: Split component into its own package (#6187) The import path for the component module can now be access directly: - `go.opentelemetry.io/collector/component` - `consumer`: Split consumer into its own package (#6186) The import path for the consumer module can now be accessed directly: - `go.opentelemetry.io/collector/consumer` - `featuregate`: Split featuregate into its own package (#6526) The import path for the featuregate module can now be accessed directly: - `go.opentelemetry.io/collector/featuregate` ### 🧰 Bug fixes 🧰 - `service`: Disallow duplicate references to processors within a single pipeline (#6540) ## v0.64.1 ### 🧰 Bug fixes 🧰 - `loggingexporter`: Fix logging exporter to not mutate the data (#6420) ## 0.64.0 ### 🛑 Breaking changes 🛑 - `config`: Remove already deprecates `config.Service`. (#6395) - `pdata`: Change output of String() method of the following enum types to more a concise form: (#6251) - plog.SeverityNumber - ptrace.SpanKind - ptrace.StatusCode - `config`: Remove already deprecates `config.Config`. (#6394) - `pdata`: Remove deprecated code from pdata (#6417) - `p[trace|metric|log]otlp.[Request|Response]` - `p[trace|metric|log]otlp.New[Request|Response]` - `p[trace|metric|log]otlp.NewRequestFrom[Traces|Metrics|Logs]` - `p[trace|metric|log]otlp.NewClient` - `p[trace|metric|log]New[JSON|Proto][Marshaler|Unmarshaler]` - `extension`: Splitting ballast/zpages extension into their own modules (#6191) The import path for the extension modules can now be accessed directly: - `go.opentelemetry.io/collector/extension/ballastextension` - `go.opentelemetry.io/collector/extension/zpagesextension` If using one of these extensions, modify your Collector builder configuration to use `gomod` directly, such as: - `gomod: go.opentelemetry.io/collector/extension/ballastextension v0.64.0` - `processor`: Splitting batch/memorylimiter processors into their own modules (#6188, #6192, #6193) The import path for the processor modules can now be access directly: - `go.opentelemetry.io/collector/processor/batchprocessor` - `go.opentelemetry.io/collector/processor/memorylimiter` If using this processor, modify your Collector builder configuration to use `gomod` directly, such as: - `gomod: go.opentelemetry.io/collector/processor/batchprocessor v0.64.0` - `otlpreceiver`: Splitting otlp receiver into its own module (#6190) The import path for the OTLP receiver can now be access directly: - `go.opentelemetry.io/collector/receiver/otlpreceiver` If using this receiver, modify your Collector builder configuration to use `gomod` directly, such as: - `gomod: go.opentelemetry.io/collector/receiver/otlpreceiver v0.64.0` - `confmap`: Remove unused public member `sync.Mutex` from `confmap.Resolver`. (#6489) This is an exception from the deprecation rule since this is not used anywhere and it is very unlikely that is used by external users. ### 🚩 Deprecations 🚩 - `config`: Deprecate multiple types and funcs in `config` package (#6422) - config.ComponentID => component.ID - config.Type => component.Type - config.DataType => component.DataType - config.[Traces|Metrics|Logs]DataType => component.DataType[Traces|Metrics|Logs] - config.Receiver => component.ReceiverConfig - config.UnmarshalReceiver => component.UnmarshalReceiverConfig - config.Processor => component.ProcessorConfig - config.UnmarshalProcessor => component.UnmarshalProcessorConfig - config.Exporter => component.ExporterConfig - config.UnmarshalExporter => component.UnmarshalExporterConfig - config.Extension => component.ExtensionConfig - config.UnmarshalExtension => component.UnmarshalExtensionConfig - `obsreport`: deprecate `obsreport.New[Receiver|Scraper|Processor|Exporter]` in favor of `obsreport.MustNew[Receiver|Scraper|Processor|Exporter]` (#6458) - `config/configgrpc`: Provide better helpers for configgrpc, consistent with confighttp (#6441) - Deprecate `GRPCClientSettings.ToDialOptions` in favor of `GRPCClientSettings.ToClientConn`. - Deprecate `GRPCServerSettings.ToServerOption` in favor of `GRPCServerSettings.ToServer`. - `featuregates`: Removing Gates being configurable externally to the Registry (#6167) - Deprecate `Gate.ID` in favour of `Registry.RegisterID` - Deprecate `Gate.Enabled` in favour of `Gate.IsEnabled()` - Deprecate `Gate.Description` in favour of `WithRegisterDescription` to be used with `Registry.RegisterID` - Deprecate `Registry.Register` in favour of `Registry.RegisterID` - Deprecate `Registry.MustRegister` in favour of `Registry.MustRegisterID` ### 💡 Enhancements 💡 - `service/telemetry`: Allow to configure sampling config for logs. (#4554) - `featuregates`: Extend feature gate definition to include support for issue links and expected deprecated version (#6167) - `receiver/otlp`: Add warning when using unspecified (`0.0.0.0`) address on HTTP or gRPC servers (#6151) - `obsreport`: Instrument `obsreport.Exporter` metrics with otel-go (#6346) - `config`: Add validation for empty address [telemetry::metrics::address] (#5661) ### 🧰 Bug fixes 🧰 - `cgroups`: split line into exactly 3 parts while parsing /proc/{pid}/cgroup (#6389) - `cgroups`: Use int64 for cgroup v1 parsing (#6443) ## v0.63.1 ### 🧰 Bug fixes 🧰 - `service`: Fix running collector as a windows service. (#6433) ## v0.63.0 ### 🛑 Breaking changes 🛑 - `pdata`: JSON marshaler emits enums as ints per spec requirements. This may be a breaking change if receivers were not confirming with the spec. (#6338) - `confmap`: Remove deprecated `confmap.Conf.UnmarshalExact` API in 0.62.0 (#6315) - `pdata`: Remove API deprecated in 0.62.0 (#6314) - Remove deprecated `pcommon.NewValueString` - Remove deprecated `pcommon.Map.PutString` - Remove deprecated `plog.SeverityNumberUndefined` - Remove deprecated `p[metric|log|trace]otlp.RegisterServer` - Remove deprecated `pmetric.[Default]?MetricDataPointFlags` - Remove deprecated `pmetric.MetricAggregationTemporality*` - Remove deprecated `pmetric.MetricTypeNone` - Remove deprecated `pmetric.NumberDataPointValueTypeNone` - Remove deprecated `pmetric.ExemplarValueTypeNone` - Remove deprecated `pmetric.[New]?Buckets` - Remove deprecated `pmetric.[New]?ValueAtQuantile` - Remove deprecated `pmetric.[New]?ValueAtQuantileSlice` - Remove deprecated `ptrace.[New]?SpanStatus` - `exporter`: Splitting otlp, otlphttp and logging exporters into their own modules (#6343) The import path for these exporters can now be access directly: - `go.opentelemetry.io/collector/exporter/loggingexporter` - `go.opentelemetry.io/collector/exporter/otlpexporter` - `go.opentelemetry.io/collector/exporter/otlphttpexporter` If using these exporters, modify your Collector builder configuration to use `gomod` directly, such as: - `gomod: go.opentelemetry.io/collector/exporter/otlpexporter v0.63.0` ### 🚩 Deprecations 🚩 - `overwritepropertiesconverter`: Deprecate `overwritepropertiesconverter`, only used by non builder distributions. (#6294) - `pdata`: Add `Export` prefix to `p[trace|metric|log]otlp.[Request|Response]` (#6365) - Deprecate `p[trace|metric|log]otlp.[Request|Response]` in favor of `p[trace|metric|log]otlp.Export[Request|Response]` - Deprecate `p[trace|metric|log]otlp.New[Request|Response]` in favor of `p[trace|metric|log]otlp.NewExport[Request|Response]` - Deprecate `p[trace|metric|log]otlp.NewRequestFrom[Traces|Metrics|Logs]` in favor of `p[trace|metric|log]otlp.NewExportRequestFrom[Traces|Metrics|Logs]` - `pdata`: Deprecate `p[trace|metric|log]otlp.NewClient` in favor of `p[trace|metric|log]otlp.NewGRPCClient` (#6350) - `exporter/logging`: Deprecate 'loglevel' in favor of 'verbosity' option (#5878) - `pdata`: Deprecate `New[JSON|Proto][Marshaler|Unmarshaler]` in favor of exposing the underlying structs (#6340) ### 💡 Enhancements 💡 - `pdata`: Introduce partial success fields in ExportResponse. (#5815, #5816, #6365) - `obsreport`: Instrument `obsreport.Receiver` metrics with otel-go (#6222) - `service/telemetry`: Move logging and tracing initialization to service/telemetry (#5564) - `confmap`: Fail fast when a resolver has URIs with unsupported schemes. (#6274) - `service`: Use the same `prometheus.Registry` for the OpenCensus and OpenTelemetry Go prometheus exporters to act as a bridge for internal telemetry (#6297) ### 🧰 Bug fixes 🧰 - `pdata`: Because of wrong deprecation/rename in proto, services still send the fake 1000 proto id. See https://github.com/open-telemetry/opentelemetry-proto/issues/431 (#6342) - `confmap`: When a sub-config implements Unmarshaler, do not reinitialized it unless necessary. (#6392) - `pdata`: Enable enums as ints for otlp messages, switch to jsoniter for responses. (#6345) - `collector`: Fixed collector service not cleaning up if it failed during Start (#6352) ## v0.62.1 Beta - Fix support for new line in config URI location. (#6306) ## v0.62.0 Beta ### 🛑 Breaking changes 🛑 - Delete deprecated `go.opentelemetry.io/collector/service/featuregate`. (#6178) - Delete deprecated `pmetric.OptionalType`. (#6178) - Delete deprecated `ptrace.Span[Link]?.TraceStateStruct`. (#6178) - Delete deprecated `pcommon.NewValueBytesEmpty`. (#6178) - Delete deprecated `pmetric.MetricDataType`. (#6178) - Delete deprecated `pmetric.Metric.DataType()`. (#6178) - Delete deprecated `pmetric.NumberDataPoint.[Set]?[Int|Double]Val()`. (#6178) - Delete deprecated `pmetric.Exemplar.[Set]?[Int|Double]Val()`. (#6178) - Delete deprecated `p[metric|log|trace]otlp.[Client|Server]`. (#6178) - Delete deprecated pdata Clone methods. (#6178) - Delete deprecated `pcommon.Value` getter/setter methods with `Val` suffix. (#6178) - Delete deprecated `StringVal` and `SetStringVal` methods. (#6178) - Delete deprecated `ValueTypeString` method. (#6178) - Change AggregationTemporality.String to simpler, easier to read. (#6117) - Update `pcommon.ValueType.String` output to string representation of corresponding type identifiers. The following values will be returned: (#6247) - ValueTypeEmpty.String() -> "Empty" - ValueTypeStr.String() -> "Str" - ValueTypeBool.String() -> "Bool" - ValueTypeInt.String() -> "Int" - ValueTypeDouble.String() -> "Double" - ValueTypeMap.String() -> "Map" - ValueTypeSlice.String() -> "Slice" - ValueTypeBytes.String() -> "Bytes" - Rename output of `[MetricType|NumberDataPointValueType|ExemplarValueType].String()` for zero values from `"None"` to `"Empty"` (#6270) ### 🚩 Deprecations 🚩 - Deprecate `p[metric|log|trace]otlp.RegisterServer` in favor of `p[metric|log|trace]otlp.RegisterGRPCServer` (#6182) - Deprecate `pcommon.Map.PutString` in favor of `pcommon.Map.PutStr` (#6210) - Deprecate `pcommon.NewValueString` in favor of `pcommon.NewValueStr` (#6209) - Deprecate `pmetric.MetricAggregationTemporality` enum type in favor of `pmetric.AggregationTemporality` (#6253) - Deprecate `confmap.Conf.UnmarshalExact` in favor of `confmap.Conf.Unmarshal(.., WithErrorUnused)` (#6231) - Deprecate `pmetric.[Default]?MetricDataPointFlags` favor of `pmetric.[Default]?DataPointFlags` (#6259) - Deprecate `ptrace.[New]?SpanStatus` favor of `ptrace.[New]?Status` (#6258) - Deprecate `pmetric.[New]?Buckets` in favor of `pmetric.[New]?ExponentialHistogramDataPointBuckets` (#6261) - Deprecate `plog.SeverityNumberUndefined` in favor of `plog.SeverityNumberUnspecified` (#6269) - Deprecate `pmetric.[New]?ValueAtQuantile[Slice]?` in favor of `pmetric.[New]?SummaryDataPointValueAtQuantile[Slice]?` (#6262) - Deprecate enum zero constants ending with `None` (#6270) - Deprecate `pmetric.MetricTypeNone` in favor of `pmetric.MetricTypeEmpty` - Deprecate `pmetric.NumberDataPointValueTypeNone` in favor of `pmetric.NumberDataPointValueTypeEmpty` - Deprecate `pmetric.ExemplarValueTypeNone` in favor of `pmetric.ExemplarValueTypeEmpty` ### 💡 Enhancements 💡 - Add config marshaler (#5566) - Add semantic conventions for specification v1.10-v1.13 (#6213) - `receiver/otlp`: Make logs related to gRCPC and HTTP server startup clearer (#6174) - Add prometheus metric prefix to Collector's own telemetry when using OpenTelemetry for internal telemetry (#6223) - `exporter/logging`: Apply consistent rendering of map values (#6244) - Add support in the confmap.Resolver to expand embedded config URIs inside configuration. (#6276) ### 🧰 Bug fixes 🧰 - Fixed bug where `telemetryInitializer` is not cleaned up when `newService` errors (#6239) ## v0.61.0 Beta ### 🛑 Breaking changes 🛑 - Change `ptrace.Span[Link]?.TraceState` signature to match `ptrace.Span[Link]?.TraceStateStruct` (#6085) - Delete deprecated `pmetric.NewMetricDataPointFlagsImmutable` func. (#6097) - Delete deprecated `pmetric.*DataPoint.[Set]FlagsImmutable()` funcs. (#6097) - Delete deprecated `config.Unmarshalable` interface. (#6084) - Delete deprecated `p[metric|log|trace].MarshalerSizer` interfaces (#6083) - Delete deprecated `pcommon.Map.Insert*` funcs. (#6088) - Delete deprecated `pcommon.Map.Upsert*` funcs. (#6088) - Delete deprecated `pcommon.Map.Update*` funcs. (#6088) - Change `pcommon.NewValueBytes` signature to match `pcommon.NewValueBytesEmpty`. (#6088) - Delete deprecated `pcommon.Empty[Trace|Span]ID`. (#6098) - Delete deprecated `pcommon.[Trace|Span]ID.Bytes()`. (#6098) - Delete deprecated `pcommon.New[Trace|Span]ID()`. (#6098) - Delete deprecated `pcommon.Value.SetBytesVal`. (#6088) - Delete deprecated `pmetric.Metric.SetDataType`. (#6095) - Delete deprecated `plog.LogRecord.[Set]FlagStruct` funcs. (#6100) - Delete deprecated `pcommon.ImmutableByteSlice` and `pcommon.NewImmutableByteSlice`. (#6107) - Delete deprecated `pcommon.ImmutableFloat64Slice` and `pcommon.NewImmutableFloat64Slice`. (#6107) - Delete deprecated `pcommon.ImmutableUInt64Slice` and `pcommon.NewImmutableUInt64Slice`. (#6107) - Delete deprecated `*DataPoint.SetBucketCounts` and `*DataPoint.SetExplicitBounds`. (#6108) ### 🚩 Deprecations 🚩 - Deprecate `go.opentelemetry.io/collector/service/featuregate` in favor of `go.opentelemetry.io/collector/featuregate`. (#6094) - Deprecate `pmetric.OptionalType`, unused enum type. (#6096) - Deprecate `ptrace.Span[Link]?.TraceStateStruct` in favor of `ptrace.Span[Link]?.TraceState` (#6085) - Deprecate `pcommon.NewValueBytesEmpty` in favor of `pcommon.NewValueBytes` that now has the same signature. (#6105) - Deprecate `pmetric.MetricDataType` and related constants in favor of `pmetric.MetricType`. (#6127) - Deprecate `pmetric.Metric.DataType()` in favor of `pmetric.Metric.Type()`. (#6127) - Deprecate `pmetric.NumberDataPoint.[Set]?[Int|Double]Val()` in favor of `pmetric.NumberDataPoint.[Set]?[Int|Double]Value()`. (#6134) - Deprecate `pmetric.Exemplar.[Set]?[Int|Double]Val()` in favor of `pmetric.Exemplar.[Set]?[Int|Double]Value()`. (#6134) - Deprecate `p[metric|log|trace]otlp.[Client|Server]` in favor of `p[metric|log|trace]otlp.GRPC[Client|Server]` (#6165) - Deprecate pdata Clone methods in favor of CopyTo for consistency with other pdata structs (#6164) - `pmetric.Metrics.Clone` is deprecated in favor of `pmetric.Metrics.CopyTo` - `ptrace.Traces.Clone` is deprecated in favor of `pmetric.Traces.CopyTo` - `plog.Logs.Clone` is deprecated in favor of `plogs.Logs.CopyTo` - Rename all `pcommon.Value` getter/setter methods by removing `Val` suffix. (#6092) - Old methods with `Val` suffix are deprecated. - `StringVal` and `SetStringVal` are deprecated in favor of `Str` and `SetStr` to avoid implementing `fmt.Stringer` interface. - Therefore, `ValueTypeString` is deprecated in favour of `ValueTypeStr` for consistency. ### 💡 Enhancements 💡 - Add AppendEmpty and EnsureCapacity method to primitive pdata slices (#6060) - Expose `AsRaw` and `FromRaw` `pcommon.Value` methods (#6090) - Convert `ValueTypeBytes` attributes in logging exporter (#6153) - service.name Resource attribute is added to Collector's own telemetry, defaults to the value of `BuildInfo.Command` and can be overridden in the config (#6152) - Updated how `telemetryInitializer` is created so it's instanced per Collector instance rather than global to the process (#6138) ## v0.60.0 Beta ### 🛑 Breaking changes 🛑 - Replace deprecated `*DataPoint.Flags()` with `*DataPoint.[Set]FlagsImmutable()`. (#6017) - Remove deprecated `MetricDataPointFlagsStruct` struct and `NewMetricDataPointFlagsStruct` func. (#6017) - Replace deprecated `MetricDataPointFlags` with `MetricDataPointFlagsImmutable`. (#6017) - Replace deprecated `LogRecord.[Set]Flags()` with `LogRecord.[Set]FlagsStruct()`. (#6007) - Remove deprecated components helpers funcs (#6006) - `exporterhelper.New[Traces|Metrics|Logs]ExporterWithContext` - `processorhelper.New[Traces|Metrics|Logs]ProcessorWithCreateSettings` - `component.NewExtensionFactoryWithStabilityLevel` - Remove deprecated `pcommon.InvalidTraceID` and `pcommon.InvalidSpanID` funcs (#6008) - Remove deprecated `pcommon.Map` methods: `Update`, `Upsert`, `InsertNull` (#6019) ### 🚩 Deprecations 🚩 - Deprecate pmetric.Metric.SetDataType, in favor of empty setters for each type. (#5979) - Deprecate `p[metric|log|trace].MarshalerSizer` in favor of `p[metric|log|trace].MarshalSizer`. (#6033) - Deprecate `pcommon.Map.Update+` in favor of `pcommon.Map.Get` + `pcommon.Value.Set+` (#6013) - Deprecate `pcommon.Empty[Trace|Span]ID` in favor of `pcommon.New[Trace|Span]IDEmpty` (#6008) - Deprecate `pcommon.[Trace|Span]ID.Bytes` in favor direct conversion. (#6008) - Deprecate `pcommon.New[Trace|Span]ID` in favor direct conversion. (#6008) - Deprecate `MetricDataPointFlagsImmutable` type. (#6017) - Deprecate `*DataPoint.[Set]FlagsImmutable()` funcs in favor of `*DataPoint.[Set]Flags()`. (#6017) - Deprecate `LogRecord.FlagsStruct()` and `LogRecord.SetFlagsStruct()` in favor of `LogRecord.Flags()` and `LogRecord.SetFlags()`. (#6007) - Deprecate `config.Unmarshallable` in favor of `confmap.Unmarshaler`. (#6031) - Primitive slice wrapper are now mutable (#5971): - `pcommon.ImmutableByteSlice` is deprecated in favor of `pcommon.ByteSlice` - `pcommon.ImmutableFloat64Slice` is deprecated in favor of `pcommon.Float64Slice` - `pcommon.ImmutableUInt64Slice` is deprecated in favor of `pcommon.UInt64Slice` - Temporarily deprecate `pcommon.NewValueBytes` that will be replaced with `pcommon.NewValueBytesEmpty` in 0.60.0 - Deprecate `pcommon.Map.UpsertBytes` in favor of `pcommon.Map.PutEmptyBytes` (#6064) - Deprecate `pcommon.Value.SetBytesVal` in favor of `pcommon.Value.SetEmptyBytesVal` - Deprecate `pcommon.New[Slice|Map]FromRaw` functions in favor of `New[Slice|Map]().FromRaw` (#6045) - Deprecate `pcommon.Map.Insert*` methods (#6051) - Deprecate `pcommon.Map.Upsert*` methods in favor of `pcommon.Map.Put*` (#6064) - Deprecate `ptrace.TraceState` in favor of `pcommon.TraceState`. (#6052) - `ptrace.Span.TraceState` in favor of `ptrace.Span.TraceStateStruct().AsRaw()` - `ptrace.Span.SetTraceState` in favor of `ptrace.Span.TraceStateStruct().FromRaw` - `ptrace.SpanLink.TraceState` in favor of `ptrace.SpanLink.TraceStateStruct().AsRaw()` - `ptrace.SpanLink.SetTraceState` in favor of `ptrace.SpanLink.TraceStateStruct().FromRaw` - `TraceStateStruct` is a temporary name that will be replaced back to `TraceState` in the next release. ### 💡 Enhancements 💡 - Add `skip-get-modules` builder flag to support isolated environment executions (#6009) - Skip unnecessary Go binary path validation when the builder is used with `skip-compilation` and `skip-get-modules` flags (#6026) - Make the otlpreceiver support to use jsoniter to unmarshal JSON payloads. (#6040) - Add mapstructure hook function for confmap.Unmarshaler interface (#6029) - Add CopyTo and MoveTo methods to primitive slices (#6044) - Add support to unmarshalls bytes into plogs.Logs with `jsoniter` in jsonUnmarshaler (#6021) - Instead of exiting, `ocb` now generates a default Collector when no build configuration is supplied (#5752) ### 🧰 Bug fixes 🧰 - otlpjson: Correctly skip unknown JSON value types. (#6038) - Fix reading resource attributes from trace JSON. (#6023) ## v0.59.0 Beta ### 🛑 Breaking changes 🛑 - Remove deprecated fields/funcs from `service` (#5907) - Remove `ConfigProviderSettings.Location` - Remove `ConfigProviderSettings.MapProviders` - Remove `ConfigProviderSettings.MapConverters` - Remove `featuregate.Registry.MustAppy` - Remove deprecated funcs from `pdata` module. (#5911) - Remove `pmetric.MetricDataPointFlags.String()` - Remove `pmetric.NumberDataPoint.FlagsStruct()` - Remove `pmetric.HistogramDataPoint.FlagsStruct()` - Remove `pmetric.ExponentialHistogramDataPoint.FlagsStruct()` - Remove `pmetric.SummaryDataPoint.FlagsStruct()` - Remove deprecated settings from `obsreport`, `ProcessorSettings.Level` and `ExporterSettings.Level` (#5918) - Replace `processorhelper.New[Traces|Metrics|Logs]Exporter` with `processorhelper.New[Traces|Metrics|Logs]ProcessorWithCreateSettings` definition (#5915) - Replace `exporterhelper.New[Traces|Metrics|Logs]Exporter` with `exporterhelper.New[Traces|Metrics|Logs]ExporterWithContext` definition (#5914) - Replace ``component.NewExtensionFactory`` with `component.NewExtensionFactoryWithStabilityLevel` definition (#5917) - Set TLS 1.2 as default for `min_version` for TLS configuration in case this property is not defined (affects servers). (#5956) ### 🚩 Deprecations 🚩 - Deprecate `processorhelper.New[Traces|Metrics|Logs]ProcessorWithCreateSettings` in favor of `processorhelper.New[Traces|Metrics|Logs]Exporter` (#5915) - Deprecates `LogRecord.Flags()` and `LogRecord.SetFlags()` in favor of `LogRecord.FlagsStruct()` and `LogRecord.SetFlagsStruct()`. (#5972) - Deprecate `exporterhelper.New[Traces|Metrics|Logs]ExporterWithContext` in favor of `exporterhelper.New[Traces|Metrics|Logs]Exporter` (#5914) - Deprecate `component.NewExtensionFactoryWithStabilityLevel` in favor of `component.NewExtensionFactory` (#5917) - Deprecate `plog.SeverityNumber[UPPERCASE]` constants (#5927) - Deprecate `pcommon.Map.InsertNull` method (#5955) - Deprecate FlagsStruct types (#5933): - `MetricDataPointFlagsStruct` -> `MetricDataPointFlags` - `NewMetricDataPointFlagsStruct` -> `NewMetricDataPointFlags` - Deprecate builder distribution flags, use configuration. (#5946) - Enforce naming conventions for Invalid[Trace|Span]ID: (#5969) - Deprecate funcs `pcommon.InvalidTraceID` and `pcommon.InvalidSpanID` in favor of vars `pcommon.EmptyTraceID` and `pcommon.EmptySpanID` - Deprecate `Update` and `Upsert` methods of `pcommon.Map` (#5975) - Deprecated the current MetricDataPointFlags API. The new API provides functions to check and set Flags. (#5999) - `NumberDataPoint.Flags` -> `NumberDataPoint.FlagsImmutable` - `HistogramDataPoint.Flags` -> `HistogramDataPoint.FlagsImmutable` - `ExponentialHistogramDataPoint.Flags` -> `ExponentialHistogramDataPoint.FlagsImmutable` - `SummaryDataPoint.Flags` -> `SummaryDataPoint.FlagsImmutable` - `MetricDataPointFlags` -> `MetricDataPointFlagsImmutable` - `NewMetricDataPointFlags` -> `MetricDataPointFlagsImmutable` ### 💡 Enhancements 💡 - Added `MarshalerSizer` interface to `ptrace`, `plog`, and `pmetric` packages. `NewProtoMarshaler` now returns a `MarshalerSizer` (#5929) - Add support to unmarshalls bytes into pmetric.Metrics with `jsoniter` in jsonUnmarshaler(#5433) - Add httpprovider to allow loading config files stored in HTTP (#5810) - Added `service.telemetry.traces.propagators` configuration to set propagators for collector's internal spans. (#5572) - Remove unnecessary duplicate code and allocations for reading enums in JSON. (#5928) - Add "dist.build_tags" configuration option to support passing go build flags to builder. (#5659) - Add an AsRaw func on the flags, lots of places to encode these flags. (#5934) - Change pdata generated types to use type definition instead of aliases. (#5936) - Improves documentation, and makes code easier to read/understand. - Log `InstrumentationScope` attributes in `loggingexporter` (#5976) - Add `UpsertEmpty`, `UpsertEmptyMap` and `UpsertEmptySlice` methods to `pcommon.Map` (#5975) - Add `SetEmptyMapVal` and `SetEmptySliceVal` methods to `pcommon.Value` (#5975) ### 🧰 Bug fixes 🧰 - Fix reading scope attributes for trace JSON, remove duplicate code. (#5930) - otlpjson/trace: skip unknown fields instead of error. (#5931) - Fix bug in setting the correct collector state after a configuration change event. (#5830) - Fix json trace unmarshalling for numbers (#5924): - Accept both string and number for float64. - Accept both string and number for int32/uint32. - Read uint64 numbers without converting from int64. - Fix persistent storage client not closing when shutting down (#6003) ## v0.58.0 Beta ### 🛑 Breaking changes 🛑 - Remove the InstrumentationLibrary to Scope translation (part of transition to OTLP 0.19). (#5819) - This has a side effect that when sending JSON encoded telemetry using OTLP proto <= 0.15.0, telemetry will be dropped. - Require the storage to be explicitly set for the (experimental) persistent queue (#5784) - Remove deprecated `confighttp.HTTPClientSettings.ToClientWithHost` (#5803) - Remove deprecated component stability helpers (#5802): - `component.WithTracesExporterAndStabilityLevel` - `component.WithMetricsExporterAndStabilityLevel` - `component.WithLogsExporterAndStabilityLevel` - `component.WithTracesReceiverAndStabilityLevel` - `component.WithMetricsReceiverAndStabilityLevel` - `component.WithLogsReceiverAndStabilityLevel` - `component.WithTracesProcessorAndStabilityLevel` - `component.WithMetricsProcessorAndStabilityLevel` - `component.WithLogsProcessorAndStabilityLevel` - ABI breaking change: `featuregate.Registry.Apply` returns error now. - Update minimum go version to 1.18 (#5795) - Remove deprecated `Flags` API from pdata (#5814) - Change `confmap.Provider` to return pointer to `Retrieved` (#5839) ### 🚩 Deprecations 🚩 - Deprecate duplicate settings in service.ConfigProvider, embed ResolverSettings (#5843) - Deprecate `featuregate.Registry.MustApply` in favor of `featuregate.Registry.Apply`. (#5801) - Deprecate the `component.Factory.StabilityLevel(config.DataType)` in favor of Stability per component (#5762): - `component.ExporterFactory.TracesExporterStability` - `component.ExporterFactory.MetricsExporterStability` - `component.ExporterFactory.LogsExporterStability` - `component.ProcessorFactory.TracesProcessorStability` - `component.ProcessorFactory.MetricsProcessorStability` - `component.ProcessorFactory.LogsProcessorStability` - `component.ReceiverFactory.TracesReceiverStability` - `component.ReceiverFactory.MetricsReceiverStability` - `component.ReceiverFactory.LogsReceiverStability` - Deprecate `obsreport.ProcessorSettings.Level` and `obsreport.ExporterSettings.Level`, use MetricsLevel from CreateSettings (#5824) - Deprecate `processorhelper.New[Traces|Metrics|Logs]Processor` in favor of `processorhelper.New[Traces|Metrics|Logs]ProcessorWithCreateSettings` (#5833) - Deprecate MetricDataPointFlags.String(), no other pdata flags have this method (#5868) - Deprecates `FlagsStruct` in favor of `Flags` (#5842) - `FlagsStruct` -> `Flags` - Deprecate `exporterhelper.New[Traces|Metrics|Logs]Exporter` in favor of `exporterhelper.New[Traces|Metrics|Logs]ExporterWithContext` (#5834) ### 💡 Enhancements 💡 - Enable persistent queue in the build by default (#5828) - Bump to opentelemetry-proto v0.19.0. (#5823) - Expose `Scope.Attributes` in pdata (#5826) - Remove unnecessary limitation on `pcommon.Value.Equal` that slices have only primitive values. (#5865) - Add support to handle 404, 405 http error code as permanent errors in OTLP exporter (#5827) - Enforce scheme name restrictions to all `confmap.Provider` implementations. (#5861) ## v0.57.2 Beta See the changelog for v0.57.0. ## v0.57.1 Beta This was a failed release. ## v0.57.0 Beta There isn't a valid core binary for this release. Use v0.57.2 instead. ### 🛑 Breaking changes 🛑 - Remove deprecated funcs/types from service related to `Config` (#5755) - Change`confighttp.ToClient` to accept a `component.Host` (#5737) - Remove deprecated funcs from pdata related to mutable slices (#5754) - Change the following deprecated component functions to ensure a stability level is set: - `component.WithTracesExporter` - `component.WithMetricsExporter` - `component.WithLogsExporter` - `component.WithTracesReceiver` - `component.WithMetricsReceiver` - `component.WithLogsReceiver` - `component.WithTracesProcessor` - `component.WithMetricsProcessor` - `component.WithLogsProcessor` ### 🚩 Deprecations 🚩 - Deprecated the current Flag API. The new API provides functions to check and set Flags (#5790) (#5602): - `NumberDataPoint.Flags` -> `NumberDataPoint.FlagsStruct` - `NumberDataPoint.SetFlags` -> `NumberDataPoint.FlagsStruct` - `HistogramDataPoint.Flags` -> `HistogramDataPoint.FlagsStruct` - `HistogramDataPoint.SetFlags` -> `HistogramDataPoint.FlagsStruct` - `ExponentialHistogramDataPoint.Flags` -> `ExponentialHistogramDataPoint.FlagsStruct` - `ExponentialHistogramDataPoint.SetFlags` -> `ExponentialHistogramDataPoint.FlagsStruct` - `SummaryDataPoint.Flags` -> `SummaryDataPoint.FlagsStruct` - `SummaryDataPoint.SetFlags` -> `SummaryDataPoint.FlagsStruct` - `MetricDataPointFlags` -> `MetricDataPointFlagsStruct` - `NewMetricDataPointFlags` -> `NewMetricDataPointFlagsStruct` - `MetricDataPointFlagsNone` -> `MetricDataPointFlagsStruct.NoRecordedValue` - `MetricDataPointFlagNoRecordedValue` -> `MetricDataPointFlagsStruct.NoRecordedValue` - `MetricDataPointFlag` - Deprecate the following component functions added to ensure a stability level is set: - `component.WithTracesExporterAndStabilityLevel` -> `component.WithTracesExporter` - `component.WithMetricsExporterAndStabilityLevel` -> `component.WithMetricsExporter` - `component.WithLogsExporterAndStabilityLevel` -> `component.WithLogsExporter` - `component.WithTracesReceiverAndStabilityLevel` -> `component.WithTracesReceiver` - `component.WithMetricsReceiverAndStabilityLevel` -> `component.WithMetricsReceiver` - `component.WithLogsReceiverAndStabilityLevel` -> `component.WithLogsReceiver` - `component.WithTracesProcessorAndStabilityLevel` -> `component.WithTracesProcessor` - `component.WithMetricsProcessorAndStabilityLevel` -> `component.WithMetricsProcessor` - `component.WithLogsProcessorAndStabilityLevel` -> `component.WithLogsProcessor` ### 💡 Enhancements 💡 - Make the in-memory and persistent queues more consistent (#5764) - `ocb` now exits with an error if it fails to load the build configuration. (#5731) - Deprecate `HTTPClientSettings.ToClientWithHost` (#5737) ### 🧰 Bug fixes 🧰 - Fix bug in ocb where flags did not take precedence. (#5726) ## v0.56.0 Beta ### 💡 Enhancements 💡 - Add `linux-ppc64le` architecture to cross build tests in CI (#5645) - `client`: perform case insensitive lookups in case the requested metadata value isn't found (#5646) - `loggingexporter`: Decouple `loglevel` field from level of logged messages (#5678) - Expose `pcommon.NewSliceFromRaw` function (#5679) - `loggingexporter`: create the exporter's logger from the service's logger (#5677) - Add `otelcol_exporter_queue_capacity` metrics show the collector's exporter queue capacity (#5475) - Add support to handle 402, 413, 414, 431 http error code as permanent errors in OTLP exporter (#5685) ### 🧰 Bug fixes 🧰 - Fix Collector panic when disabling telemetry metrics (#5642) - Fix Collector panic when featuregate value is empty (#5663) - Fix confighttp.compression panic due to nil request.Body. (#5628) ## v0.55.0 Beta ### 🛑 Breaking changes 🛑 - Remove deprecated `config.ServiceTelemetry` (#5565) - Remove deprecated `config.ServiceTelemetryLogs` (#5565) - Remove deprecated `config.ServiceTelemetryMetrics` (#5565) ### 🚩 Deprecations 🚩 - Deprecate `service.ConfigServiceTelemetry`, `service.ConfigServiceTelemetryLogs`, and `service.ConfigServiceTelemetryMetrics` (#5565) - Deprecate the following component functions to ensure a stability level is set (#5580): - `component.WithTracesExporter` -> `component.WithTracesExporterAndStabilityLevel` - `component.WithMetricsExporter` -> `component.WithMetricsExporterAndStabilityLevel` - `component.WithLogsExporter` -> `component.WithLogsExporterAndStabilityLevel` - `component.WithTracesReceiver` -> `component.WithTracesReceiverAndStabilityLevel` - `component.WithMetricsReceiver` -> `component.WithMetricsReceiverAndStabilityLevel` - `component.WithLogsReceiver` -> `component.WithLogsReceiverAndStabilityLevel` - `component.WithTracesProcessor` -> `component.WithTracesProcessorAndStabilityLevel` - `component.WithMetricsProcessor` -> `component.WithMetricsProcessorAndStabilityLevel` - `component.WithLogsProcessor` -> `component.WithLogsProcessorAndStabilityLevel` - Deprecate `Registry.Apply` in `service.featuregate` (#5660) ### 💡 Enhancements 💡 - Components stability levels are now logged. By default components which haven't defined their stability levels, or which are unmaintained, deprecated or in development will log a message. (#5580) - `exporter/logging`: Skip "bad file descriptor" sync errors (#5585) ### 🧰 Bug fixes 🧰 - Fix initialization of the OpenTelemetry MetricProvider. (#5571) - Set log level for `undefined` stability level to debug. (#5635) ## v0.54.0 Beta ### 🛑 Breaking changes 🛑 - Remove deprecated `GetLogger`. (#5504) - Remove deprecated `configtest.LoadConfigMap` (#5505) - Remove deprecated `config.Map` (#5505) - Remove deprecated `config.MapProvider` (#5505) - Remove deprecated `config.MapConverter` (#5505) - Remove deprecated `config.Received` (#5505) - Remove deprecated `config.CloseFunc` (#5505) - Deprecated `pcommon.Value.NewValueBytes` is brought back taking `pcommon.ImmutableByteSlice` as an argument instead of `[]byte` (#5299) ### 🚩 Deprecations 🚩 - Use immutable slices for primitive types slices to restrict mutations. (#5299) - `Value.NewValueMBytes` func is deprecated in favor of `Value.NewValueBytes` func that takes `ImmutableByteSlice` instead of `[]byte` - `Value.SetMBytesVal` func is deprecated in favor of `Value.SetBytesVal` func that takes `pcommon.ImmutableByteSlice` instead of []byte. - `Value.BytesVal` func is deprecated in favor of `Value.BytesVal` func that returns `pcommon.ImmutableByteSlice` instead of []byte. - `.SetMBucketCounts` funcs are deprecated in favor of `.SetBucketCounts` funcs that take `pcommon.ImmutableUInt64Slice` instead of []uint64. - `.MBucketCounts` funcs are deprecated in favor of `.BucketCounts` funcs that return `pcommon.ImmutableUInt64Slice` instead of []uint64. - `HistogramDataPoint.SetMExplicitBounds` func is deprecated in favor of `HistogramDataPoint.SetExplicitBounds` func that takes `pcommon.ImmutableFloat64Slice` instead of []float64. - `HistogramDataPoint.MExplicitBounds` func func is deprecated in favor of `HistogramDataPoint.ExplicitBounds` returns `pcommon.ImmutableFloat64Slice` instead of []float64. ### 💡 Enhancements 💡 - Deprecate `HTTPClientSettings.ToClient` in favor of `HTTPClientSettings.ToClientWithHost` (#5584) - Use OpenCensus `metric` package for process metrics instead of `stats` package (#5486) - Update OTLP to v0.18.0 (#5530) - Log histogram min/max fields with `logging` exporter (#5520) ### 🧰 Bug fixes 🧰 - Update sum field of exponential histograms to make it optional (#5530) - Remove redundant extension shutdown call (#5532) - Refactor pipelines builder, fix some issues (#5512) - Unconfigured receivers are not identified, this was not a real problem in final binaries since the validation of the config catch this. - Allow configurations to contain "unused" receivers. Receivers that are configured but not used in any pipeline, this was possible already for exporters and processors. - Remove the enforcement/check that Receiver factories create the same instance for the same config. ## v0.53.0 Beta ### 🛑 Breaking changes 🛑 - Remove deprecated `componenterror` package. (#5420) - Remove deprecated `config.MapConverterFunc`. (#5419) - Remove `AddCollectorVersionTag`, enabled for long time already. (#5471) ### 🚩 Deprecations 🚩 - Move `config.Map` to its own package `confmap` which does not depend on any component concept (#5237) - `config.Map` -> `confmap.ConfMap` - `config.MapProvider` -> `confmap.Provider` - `config.Received` -> `confmap.Received` - `config.NewReceivedFromMap` -> `confmap.NewReceived` - `config.CloseFunc` -> `confmap.CloseFunc` - `config.ChangeEvent` -> `confmap.ChangeEvent` - `config.MapConverter` -> `confmap.Converter` - Package `envmapprovider` -> `envprovider` - Package `filemapprovider` -> `fileprovider` - Package `yamlmapprovider` -> `yamlprovider` - Package `expandmapconverter` -> `expandconverter` - Package `filemapprovider` -> `fileprovider` - Package `overwritepropertiesmapconverter` -> `overwritepropertiesconverter` - Deprecate `component.ExtensionDefaultConfigFunc` in favor of `component.ExtensionCreateDefaultConfigFunc` (#5451) - Deprecate `confmap.Received.AsMap` in favor of `confmap.Received.AsConf` (#5465) - Deprecate `confmap.Conf.Set`, not used anywhere for the moment (#5485) ### 💡 Enhancements 💡 - Move `service.mapResolver` to `confmap.Resolver` (#5444) - Add `linux-arm` architecture to cross build tests in CI (#5472) ### 🧰 Bug fixes 🧰 - Fixes the "service.version" label value for internal metrics, always was "latest" in core/contrib distros. (#5449). - Send correct batch stats when SendBatchMaxSize is set (#5385) - TLS `MinVersion` and `MaxVersion` defaults will be handled by `crypto/tls` (#5480) ## v0.52.0 Beta ### 🛑 Breaking changes 🛑 - Remove `configunmarshaler.Unmarshaler` interface, per deprecation comment (#5348) - Remove deprecated pdata funcs/structs from v0.50.0 (#5345) - Remove deprecated pdata getters and setters of primitive slice values: `Value.BytesVal`, `Value.SetBytesVal`, `Value.UpdateBytes`, `Value.InsertBytes`, `Value.UpsertBytes`, `.BucketCounts`, `.SetBucketCounts`, `HistogramDataPoint.ExplicitBounds`, `HistogramDataPoint.SetExplicitBounds` (#5347) - Remove deprecated featuregate funcs/structs from v0.50.0 (#5346) - Remove access to deprecated members of the config.Retrieved struct (#5363) - Replace usage of `config.MapConverterFunc` with `config.MapConverter` (#5382) ### 🚩 Deprecations 🚩 - Deprecate `config.Config` and `config.Service`, use `service.Config*` (#4608) - Deprecate `componenterror` package, move everything to `component` (#5383) - `pcommon.Value.NewValueBytes` is deprecated in favor of `Value.NewValueMBytes` in preparation of migration to immutable slices (#5367) ### 💡 Enhancements 💡 - Update OTLP to v0.17.0 (#5335) - Add optional min/max fields to histograms (#5399) - User-defined Resource attributes can be specified under `service.telemetry.resource` configuration key and will be included as metric labels for own telemetry. If `service.instance.id` is not specified it will be auto-generated. Previously `service.instance.id` was always auto-generated, so the default of the new behavior matches the old behavior. (#5402) ## v0.51.0 Beta ### 🛑 Breaking changes 🛑 - Remove deprecated model module, everything is available in `pdata` and `semconv`. (#5281) - Old versions of the module are still available, but no new versions will be released. - Remove deprecated LogRecord.Name field. (#5202) ### 🚩 Deprecations 🚩 - In preparation of migration to immutable slices for primitive type items, the following methods are renamed (#5344) - `Value.BytesVal` func is deprecated in favor of `Value.MBytesVal`. - `Value.SetBytesVal` func is deprecated in favor of `Value.SetMBytesVal`. - `Value.UpdateBytes` func is deprecated in favor of `Value.UpdateMBytes`. - `Value.InsertBytes` func is deprecated in favor of `Value.InsertMBytes`. - `Value.UpsertBytes` func is deprecated in favor of `Value.UpsertMBytes`. - `.BucketCounts` funcs are deprecated in favor of `.MBucketCounts`. - `.SetBucketCounts` funcs are deprecated in favor of `.SetMBucketCounts`. - `HistogramDataPoint.ExplicitBounds` func is deprecated in favor of `HistogramDataPoint.MExplicitBounds`. - `HistogramDataPoint.SetExplicitBounds` func is deprecated in favor of `HistogramDataPoint.SetMExplicitBounds`. ### 💡 Enhancements 💡 - `pdata`: Expose `pcommon.NewSliceFromRaw` and `pcommon.Slice.AsRaw` functions (#5311) ### 🧰 Bug fixes 🧰 - Fix Windows Event Logs ignoring user-specified logging options (#5298) ## v0.50.0 Beta ### 🛑 Breaking changes 🛑 - Remove pdata deprecated funcs from 2 versions (v0.48.0) ago. (#5219) - Remove non pdata deprecated funcs/structs (#5220) - `pmetric.Exemplar.ValueType()` now returns new type `ExemplarValueType` (#5233) - Remove deprecated `Delete` pdata func in favor of `pdata.Remove` from (v0.47.0). (#5307) ### 🚩 Deprecations 🚩 - Deprecate `configunmarshaler` package, move it to internal (#5151) - Deprecate all API in `model/semconv`. The package is moved to a new `semconv` module (#5196) - Deprecate access to `config.Retrieved` fields, use the newly added funcs to interact with the internal fields (#5198) - Deprecate `potlp.Request.Set` (#5234) - `plogotlp.Request.SetLogs` func is deprecated in favor of `plogotlp.NewRequestFromLogs` - `pmetricotlp.Request.SetMetrics` func is deprecated in favor of `pmetricotlp.NewRequestFromMetrics` - `ptraceotlp.Request.SetTraces` func is deprecated in favor of `ptraceotlp.NewRequestFromTraces` - `pmetric.NumberDataPoint.ValueType()` now returns new type `NumberDataPointValueType` (#5233) - `pmetric.MetricValueType` is deprecated in favor of `NumberDataPointValueType` - `pmetric.MetricValueTypeNone` is deprecated in favor of `NumberDataPointValueTypeNone` - `pmetric.MetricValueTypeInt` is deprecated in favor of `NumberDataPointValueTypeInt` - `pmetric.MetricValueTypeDouble` is deprecated in favor of `NumberDataPointValueTypeDouble` - Deprecate `plog.LogRecord.SetName()` function (#5230) - Deprecate global `featuregate` funcs in favor of `GetRegistry` and a public `Registry` type (#5160) ### 💡 Enhancements 💡 - Add `jsoniter` Unmarshaller (#4817) - Extend config.Map.Unmarshal hook to check map key string to any TextUnmarshaler not only ComponentID (#5244) - Collector will no longer print error with stack trace when the collector is shutdown due to a context cancel. (#5258) ### 🧰 Bug fixes 🧰 - Fix translation from otlp.Request to pdata representation, changes to the returned pdata not all reflected to the otlp.Request (#5197) - `exporterhelper` now properly consumes any remaining items on stop (#5203) - `pdata`: Fix copying of `Value` with `ValueTypeBytes` type (#5267) - `pdata`: Fix copying of metric fields of primitive items slice type (#5271) ## v0.49.0 Beta ### 🛑 Breaking changes 🛑 - Remove deprecated structs/funcs from previous versions (#5131) - Do not set TraceProvider to global otel (#5138) - Remove deprecated funcs from otlpgrpc (#5144) - Add Scheme to MapProvider interface (#5068) - Do not set MeterProvider to global otel (#5146) - Make `InstrumentationLibraryToScope` helper functions unexported (#5164) - Remove Log's "ShortName" from logging exporter output (#5172) - `exporter/otlp`: Retry RESOURCE_EXHAUSTED only if the server returns RetryInfo (#5147) ### 🚩 Deprecations 🚩 - All pdata related APIs from model (model/pdata, model/otlp and model/otlpgrpc) are deprecated in favor of packages in the new pdata module separated by telemetry signal type (#5168) - `model/pdata`, `model/otlp` -> `pdata/pcommon`, `pdata/plog`, `pdata/pmetric`, `pdata/ptrace` - `model/otlpgrpc` -> `pdata/plog/plogotlp`, `pdata/pmetric/pmetricotlp`, `pdata/ptrace/ptraceotlp` - Deprecate configmapprovider package, replace with mapconverter (#5167) - Deprecate `service.MustNewConfigProvider` and `service.MustNewDefaultConfigProvider`in favor of `service.NewConfigProvider` (#4936) ### 💡 Enhancements 💡 - OTLP HTTP receiver will use HTTP/2 over TLS if client supports it (#5109) - Add `ObservedTimestamp` field to `pdata.LogRecord` (#5171) ### 🧰 Bug fixes 🧰 - Setup the correct meter provider if telemetry.useOtelForInternalMetrics featuregate enabled (#5146) - Fix pdata.Value.asRaw() to correctly return elements of Slice and Map type (#5153) - Update pdata.Slice.asRaw() to return raw representation of Slice and Map elements (#5157) - The codepath through the OTLP receiver for gRPC was not translating the InstrumentationLibrary* to Scope* (#5189) ## v0.48.0 Beta ### 🛑 Breaking changes 🛑 - Remove deprecated `consumerhelper` package (#5028) - Remove pdata `InternalRep` deprecated funcs (#5018) - Remove service/defaultcomponents deprecated package (#5019) - Remove deprecated UseOpenTelemetryForInternalMetrics (#5026) - Change outcome of `pdata.Value.MapVal()` and `pdata.Value.SliceVal()` functions misuse. In case of type mismatch, they now return an invalid zero-initialized instance instead of a detached collection (#5034) - OTLP JSON field changes following upgrade to OTLP v0.15.0: - "instrumentationLibraryLogs" is now "scopeLogs" - "instrumentationLibraryMetrics" is now "scopeMetrics" - "instrumentationLibrarySpans" is now "scopeSpans" - "instrumentationLibrary" is now "scope" - AsString for pdata.Value now returns the JSON-encoded string of floats. (#4934) ### 🚩 Deprecations 🚩 - Move MapProvider to config, split providers in their own package (#5030) - API related to `pdata.AttributeValue` is deprecated in favor of `pdata.Value` (#4978) - `pdata.AttributeValue` struct is deprecated in favor of `pdata.Value` - `pdata.AttributeValueType` type is deprecated in favor of `pdata.ValueType` - `pdata.AttributeValueType...` constants are deprecated in favor of `pdata.ValueType...` - `pdata.NewAttributeValue...` funcs are deprecated in favor of `pdata.NewValue...` - Deprecate featureflags.FlagValue.SetSlice, unnecessary public (#5053) - Remove "Attribute" part from common pdata collections names (#5001) - Deprecate `pdata.AttributeMap` struct in favor of `pdata.Map` - Deprecate `pdata.NewAttributeMap` func in favor of `pdata.NewMap` - Deprecate `pdata.NewAttributeMapFromMap` func in favor of `pdata.NewMapFromRaw` - Deprecate `pdata.AttributeValueSlice` struct in favor of `pdata.Slice` - Deprecate `pdata.NewAttributeValueSlice` func in favor of `pdata.NewSlice` - Deprecate LogRecord.Name(), it was deprecated in the data model (#5054) - Rename `Array` type of `pdata.Value` to `Slice` (#5066) - Deprecate `pdata.AttributeValueTypeArray` type in favor of `pdata.ValueTypeSlice` - Deprecate `pdata.NewAttributeValueArray` func in favor of `pdata.NewValueSlice` - Deprecate global flag in `featuregates` (#5060) - Deprecate last funcs/structs in componenthelper (#5069) - Change structs in otlpgrpc to follow standard go encoding interfaces (#5062) - Deprecate `UnmarshalJSON[Traces|Metrics|Logs][Request|Response]` in favor of `UnmarshalJSON`. - Deprecate `[Traces|Metrics|Logs][Request|Response].Marshal` in favor of `MarshalProto`. - Deprecate `UnmarshalJSON[Traces|Metrics|Logs][Request|Response]` in favor of `UnmarshalProto`. - Deprecating following pdata methods/types following OTLP v0.15.0 upgrade (#5076): - InstrumentationLibrary is now InstrumentationScope - NewInstrumentationLibrary is now NewInstrumentationScope - InstrumentationLibraryLogsSlice is now ScopeLogsSlice - NewInstrumentationLibraryLogsSlice is now NewScopeLogsSlice - InstrumentationLibraryLogs is now ScopeLogs - NewInstrumentationLibraryLogs is now NewScopeLogs - InstrumentationLibraryMetricsSlice is now ScopeMetricsSlice - NewInstrumentationLibraryMetricsSlice is now NewScopeMetricsSlice - InstrumentationLibraryMetrics is now ScopeMetrics - NewInstrumentationLibraryMetrics is now NewScopeMetrics - InstrumentationLibrarySpansSlice is now ScopeSpansSlice - NewInstrumentationLibrarySpansSlice is now NewScopeSpansSlice - InstrumentationLibrarySpans is now ScopeSpans - NewInstrumentationLibrarySpans is now NewScopeSpans ### 💡 Enhancements 💡 - Add semconv definitions for v1.9.0 (#5090) - Change outcome of `pdata.Metric.()` functions misuse. In case of type mismatch, they don't panic right away but return an invalid zero-initialized instance for consistency with other OneOf field accessors (#5035) - Update OTLP to v0.15.0 (#5064) - Adding support for transition from older versions of OTLP to OTLP v0.15.0 (#5085) ### 🧰 Bug fixes 🧰 - Add missing files for semconv definitions v1.7.0 and v1.8.0 (#5091) - The `featuregates` were not configured from the "--feature-gates" flag on windows service (#5060) - Fix Semantic Convention Schema URL definition for 1.5.0 and 1.6.1 versions (#5103) ## v0.47.0 Beta ### 🛑 Breaking changes 🛑 - Remove `Type` funcs in pdata (#4933) - Remove all deprecated funcs/structs from v0.46.0 (#4995) ### 🚩 Deprecations 🚩 - pdata: deprecate funcs working with InternalRep (#4957) - Deprecate `pdata.AttributeMap.Delete` in favor of `pdata.AttributeMap.Remove` (#4914) - Deprecate consumerhelper, move helpers to consumer (#5006) ### 💡 Enhancements 💡 - Add `pdata.AttributeMap.RemoveIf`, which is a more performant way to remove multiple keys (#4914) - Add `pipeline` key with pipeline identifier to processor loggers (#4968) - Add a new yaml provider, allows providing yaml bytes (#4998) ### 🧰 Bug fixes 🧰 - Collector `Run` will now exit when a context cancels (#4954) - Add missing droppedAttributesCount to pdata generated resource (#4979) - Collector `Run` will now set state to `Closed` if startup fails (#4974) ## v0.46.0 Beta ### 🛑 Breaking changes 🛑 - Change otel collector to enable open telemetry metrics through feature gate instead of a constant (#4912) - Remove support for legacy otlp/http port. (#4916) - Remove deprecated funcs in pdata (#4809) - Remove deprecated Retrieve funcs/calls (#4922) - Remove deprecated NewConfigProvider funcs (#4937) ### 🚩 Deprecations 🚩 - Deprecated funcs `config.DefaultConfig`, `confighttp.DefaultHTTPSettings`, `exporterhelper.DefaultTimeoutSettings`, `exporthelper.DefaultQueueSettings`, `exporterhelper.DefaultRetrySettings`, `testcomponents.DefaultFactories`, and `scraperhelper.DefaultScraperControllerSettings` in favour for their `NewDefault` method to adhere to contribution guidelines (#4865) - Deprecated funcs `componenthelper.StartFunc`, `componenthelper.ShutdownFunc` in favour of `component.StartFunc` and `component.ShutdownFunc` (#4803) - Move helpers from extensionhelper to component (#4805) - Deprecated `extensionhelper.CreateDefaultConfig` in favour of `component.ExtensionDefaultConfigFunc` - Deprecated `extensionhelper.CreateServiceExtension` in favour of `component.CreateExtensionFunc` - Deprecated `extensionhelper.NewFactory` in favour of `component.NewExtensionFactory` - Move helpers from processorhelper to component (#4889) - Deprecated `processorhelper.CreateDefaultConfig` in favour of `component.ProcessorDefaultConfigFunc` - Deprecated `processorhelper.WithTraces` in favour of `component.WithTracesProcessor` - Deprecated `processorhelper.WithMetrics` in favour of `component.WithMetricsProcessor` - Deprecated `processorhelper.WithLogs` in favour of `component.WithLogsProcessor` - Deprecated `processorhelper.NewFactory` in favour of `component.NewProcessorFactory` - Move helpers from exporterhelper to component (#4899) - Deprecated `exporterhelper.CreateDefaultConfig` in favour of `component.ExporterDefaultConfigFunc` - Deprecated `exporterhelper.WithTraces` in favour of `component.WithTracesExporter` - Deprecated `exporterhelper.WithMetrics` in favour of `component.WithMetricsExporter` - Deprecated `exporterhelper.WithLogs` in favour of `component.WithLogsExporter` - Deprecated `exporterhelper.NewFactory` in favour of `component.NewExporterFactory` - Move helpers from receiverhelper to component (#4891) - Deprecated `receiverhelper.CreateDefaultConfig` in favour of `component.ReceiverDefaultConfigFunc` - Deprecated `receiverhelper.WithTraces` in favour of `component.WithTracesReceiver` - Deprecated `receiverhelper.WithMetrics` in favour of `component.WithMetricsReceiver` - Deprecated `receiverhelper.WithLogs` in favour of `component.WithLogsReceiver` - Deprecated `receiverhelper.NewFactory` in favour of `component.NewReceiverFactory` ### 💡 Enhancements 💡 - Add validation to check at least one endpoint is specified in otlphttpexporter's configuration (#4860) - Implement default client authenticators (#4837) ## 🧰 Bug fixes 🧰 - Initialized logger with collector to avoid potential race condition panic on `Shutdown` (#4827) - In addition to traces, now logs and metrics processors will start the memory limiter. Added thread-safe logic so only the first processor can launch the `checkMemLimits` go-routine and the last processor that calls shutdown to terminate it; this is done per memory limiter instance. Added memory limiter factory to cache initiated object and be reused by similar config. This guarantees a single running `checkMemLimits` per config (#4886) - Resolved race condition in collector when calling `Shutdown` (#4878) ## v0.45.0 Beta ### 🛑 Breaking changes 🛑 - Remove deprecated funcs in configtelemetry (#4808) - `otlphttp` and `otlp` exporters enable gzip compression by default (#4632) ### 🚩 Deprecations 🚩 - Deprecate `service/defaultcomponents` go package (#4622) - Deprecate `pdata.NumberDataPoint.Type()` and `pdata.Exemplar.Type()` in favor of `NumberDataPoint.ValueType()` and `Exemplar.ValueType()` (#4850) ### 💡 Enhancements 💡 - Reject invalid queue size exporterhelper (#4799) - Transform configmapprovider.Retrieved interface to a struct (#4789) - Added feature gate summary to zpages extension (#4834) - Add support for reloading TLS certificates (#4737) ### 🧰 Bug fixes 🧰 - `confighttp`: Allow CORS requests with configured auth (#4869) ## v0.44.0 Beta ### 🛑 Breaking changes 🛑 - Updated to OTLP 0.12.0. Deprecated traces and metrics messages that existed in 0.11.0 are no longer converted to the messages and fields that replaced the deprecated ones. Received deprecated messages and fields will be now ignored. In OTLP/JSON in the instrumentationLibraryLogs object the "logs" field is now named "logRecords" (#4724) - Deprecate `service.NewWindowsService`, add `service.NewSvcHandler` (#4783). ### 🚩 Deprecations 🚩 - Deprecate `service.NewConfigProvider`, and a new version `service.MustNewConfigProvider` (#4734). ### 💡 Enhancements 💡 - Invalid requests now return an appropriate unsupported (`405`) or method not allowed (`415`) response (#4735) - `client.Info`: Add Host property for Metadata (#4736) ## v0.43.1 Beta ### 🧰 Bug fixes 🧰 - ExpandStringValues function support to map[string]interface{} (#4748) ## v0.43.0 Beta ### 🛑 Breaking changes 🛑 - Change configmapprovider.Provider to accept a location for retrieve (#4657) - Change Properties Provider to be a Converter (#4666) - Define a type `WatcherFunc` for onChange func instead of func pointer (#4656) - Remove deprecated `configtest.LoadConfig` and `configtest.LoadConfigAndValidate` (#4659) - Move service.ConfigMapConverterFunc to config.MapConverterFunc (#4673) - Add context to config.MapConverterFunc (#4678) - Builder: the skip compilation should only be supplied as a CLI flag. Previously, it was possible to specify that in the YAML file, contrary to the original intention (#4645) - Builder: Remove deprecated config option module::core (#4693) - Remove deprecate flags --metrics-level and --metrics-addr (#4695) - Usages of `--metrics-level={VALUE}` can be replaced by `--set=service.telemetry.metrics.level={VALUE}`; - Usages of `--metrics-addr={VALUE}` can be replaced by `--set=service.telemetry.metrics.address={VALUE}`; - Updated confighttp `ToClient` to support passing telemetry settings for instrumenting otlphttp exporter(#4449) - Remove support to some arches and platforms from `ocb` (opentelemetry-collector-builder) (#4710) - Remove deprecated legacy path ("v1/trace") support for otlp http receiver (#4720) - Change the `service.NewDefaultConfigProvider` to accept a slice of location strings (#4727). ### 🚩 Deprecations 🚩 - Deprecate `configtelemetry.Level.Set()` (#4700) ### 🧰 Bug fixes 🧰 - Ensure Windows path (e.g: C:) is recognized as a file path (#4726) - Fix structured logging issue for windows service (#4686) ### 💡 Enhancements 💡 - Expose experimental API `configmapprovider.NewExpandConverter()` (#4672) - `service.NewConfigProvider`: copy slice argument, disallow changes from caller to the input slice (#4729) - `confighttp` and `configgrpc`: New config option `include_metadata` to persist request metadata/headers in `client.Info.Metadata` (experimental) (#4547) - Remove expand cases that cannot happen with config.Map (#4649) - Add `max_request_body_size` to confighttp.HTTPServerSettings (#4677) - Move `compression.go` into `confighttp.go` to internalize functions in `compression.go` file. (#4651) - create `configcompression` package to manage compression methods in `confighttp` and `configgrpc` - Add support for cgroupv2 memory limit (#4654) - Enable end users to provide multiple files for config location (#4727) ## v0.42.0 Beta ### 🛑 Breaking changes 🛑 - Remove `configmapprovider.NewInMemory()` (#4507) - Disallow direct implementation of `configmapprovider.Retrieved` (#4577) - `configauth`: remove interceptor functions from the ServerAuthenticator interface (#4583) - Replace ConfigMapProvider and ConfigUnmarshaler in collector settings by one simpler ConfigProvider (#4590) - Remove deprecated consumererror.Combine (#4597) - Remove `configmapprovider.NewDefault`, `configmapprovider.NewExpand`, `configmapprovider.NewMerge` (#4600) - The merge functionality is now embedded into `service.NewConfigProvider` (#4637). - Move `configtest.LoadConfig` and `configtest.LoadConfigAndValidate` to `servicetest` (#4606) - Builder: Remove deprecated `include-core` flag (#4616) - Collector telemetry level must now be accessed through an atomic function. (#4549) ### 💡 Enhancements 💡 - `confighttp`: add client-side compression support. (#4441) - Each exporter should remove `compression` field if they have and should use `confighttp.HTTPClientSettings` - Allow more zap logger configs: `disable_caller`, `disable_stacktrace`, `output_paths`, `error_output_paths`, `initial_fields` (#1048) - Allow the custom zap logger encoding (#4532) - Collector self-metrics may now be configured through the configuration file. (#4069) - CLI flags for configuring self-metrics are deprecated and will be removed in a future release. - `service.telemetry.metrics.level` and `service.telemetry.metrics.address` should be used to configure collector self-metrics. - `configauth`: add helpers to create new server authenticators. (#4558) - Refactor `configgrpc` for compression methods (#4624) - Add an option to allow `config.Map` conversion in the `service.ConfigProvider` (#4634) - Added support to expose gRPC framework's logs as part of collector logs (#4501) - Builder: Enable unmarshal exact to help finding hard to find typos #4644 ### 🧰 Bug fixes 🧰 - Fix merge config map provider to close the watchers (#4570) - Fix expand map provider to call close on the base provider (#4571) - Fix correct the value of `otelcol_exporter_send_failed_requests` (#4629) - `otlp` receiver: Fix legacy port cfg value override and HTTP server starting bug (#4631) ## v0.41.0 Beta ### 🛑 Breaking changes 🛑 - Remove reference to `defaultcomponents` in core and deprecate `include_core` flag (#4087) - Remove `config.NewConfigMapFrom[File|Buffer]`, add testonly version (#4502) - `configtls`: TLS 1.2 is the new default minimum version (#4503) - `confighttp`: `ToServer` now accepts a `component.Host`, in line with gRPC's counterpart (#4514) - CORS configuration for OTLP/HTTP receivers has been moved into a `cors:` block, instead of individual `cors_allowed_origins` and `cors_allowed_headers` settings (#4492) ### 💡 Enhancements 💡 - OTLP/HTTP receivers now support setting the `Access-Control-Max-Age` header for CORS caching. (#4492) - `client.Info` pre-populated for all receivers using common helpers like `confighttp` and `configgrpc` (#4423) ### 🧰 Bug fixes 🧰 - Fix handling of corrupted records by persistent buffer (experimental) (#4475) ### 💡 Enhancements 💡 - Extending the contribution guide to help clarify what is acceptable defaults and recommendations. ## v0.40.0 Beta ### 🛑 Breaking changes 🛑 - Package `client` refactored (#4416) and auth data included in it (#4422). Final PR to be merged in the next release (#4423) - Remove `pdata.AttributeMap.InitFromMap` (#4429) - Updated configgrpc `ToDialOptions` to support passing providers to instrumentation library (#4451) - Make state information propagation non-blocking on the collector (#4460) ### 💡 Enhancements 💡 - Add semconv 1.7.0 and 1.8.0 (#4452) - Added `feature-gates` CLI flag for controlling feature gate state. (#4368) - Add a default user-agent header to the OTLP/gRPC and OTLP/HTTP exporters containing collector build information (#3970) ## v0.39.0 Beta ### 🛑 Breaking changes 🛑 - Remove deprecated config (already no-op) `ballast_size_mib` in memorylimiterprocessor (#4365) - Remove `config.Receivers`, `config.Exporters`, `config.Processors`, and `config.Extensions`. Use map directly (#4344) - Remove `component.BaseProcessorFactory`, use `processorhelper.NewFactory` instead (#4175) - Force usage of `exporterhelper.NewFactory` to implement `component.ExporterFactory` (#4338) - Force usage of `receiverhelper.NewFactory` to implement `component.ReceiverFactory` (#4338) - Force usage of `extensionhelper.NewFactory` to implement `component.ExtensionFactory` (#4338) - Move `service/parserprovider` package to `config/configmapprovider` (#4206) - Rename `MapProvider` interface to `Provider` - Remove `MapProvider` from helper names - Renamed slice-valued `pdata` types and functions for consistency. (#4325) - Rename `pdata.AnyValueArray` to `pdata.AttributeValueSlice` - Rename `ArrayVal()` to `SliceVal()` - Rename `SetArrayVal()` to `SetSliceVal()` - Remove `config.Pipeline.Name` (#4326) - Rename `config.Mapprovider` as `configmapprovider.Provider` (#4337) - Move `config.WatchableRetrieved` and `config.Retrieved` interfaces to `config/configmapprovider` package (#4337) - Remove `config.Pipeline.InputDataType` (#4343) - otlpexporter: Do not retry on PermissionDenied and Unauthenticated (#4349) - Enable configuring collector metrics through service config file. (#4069) - New `service::telemetry::metrics` structure added to configuration - Existing metrics configuration CLI flags are deprecated and to be removed in the future. - `--metrics-prefix` is no longer operative; the prefix is determined by the value of `service.buildInfo.Command`. - `--add-instance-id` is no longer operative; an instance ID will always be added. - Remove deprecated funcs `consumererror.As[Traces|Metrics|Logs]` (#4364) - Remove support to expand env variables in default configs (#4366) ### 💡 Enhancements 💡 - Supports more compression methods(`snappy` and `zstd`) for configgrpc, in addition to current `gzip` (#4088) - Moved the OpenTelemetry Collector Builder to core (#4307) ### 🧰 Bug fixes 🧰 - Fix AggregationTemporality and IsMonotonic when metric descriptors are split in the batch processor (#4389) ## v0.38.0 Beta ### 🛑 Breaking changes 🛑 - Removed `configauth.HTTPClientAuthenticator` and `configauth.GRPCClientAuthenticator` in favor of `configauth.ClientAuthenticator`. (#4255) - Rename `parserprovider.MapProvider` as `config.MapProvider`. (#4178) - Rename `parserprovider.Watchable` as `config.WatchableMapProvider`. (#4178) - Remove deprecated no-op flags to setup Collector's logging "--log-level", "--log-profile", "--log-format". (#4213) - Move `cmd/pdatagen` as internal package `model/internal/cmd/pdatagen`. (#4243) - Use directly the ComponentID in configauth. (#4238) - Refactor configauth, getters use the map instead of iteration. (#4234) - Change scraperhelper to follow the recommended append model for pdata. (#4202) ### 💡 Enhancements 💡 - Update proto to 0.11.0. (#4209) - Change pdata to use the newly added [Traces|Metrics|Logs]Data. (#4214) - Add ExponentialHistogram field to pdata. (#4219) - Make sure otlphttp exporter tests include TraceID and SpanID. (#4268) - Use multimod tool in release process. (#4229) - Change queue metrics to use opencensus metrics instead of stats, close to otel-go. (#4220) - Make receiver data delivery guarantees explicit (#4262) - Simplify unmarshal logic by adding more supported hooks. (#4237) - Add unmarshaler for otlpgrpc.[*]Request and otlpgrpc.[*]Response (#4215) ## v0.37.0 Beta ### 🛑 Breaking changes 🛑 - Move `configcheck.ValidateConfigFromFactories` as internal function in service package (#3876) - Rename `configparser.Parser` as `config.Map` (#4075) - Rename `component.DefaultBuildInfo()` to `component.NewDefaultBuildInfo()` (#4129) - Rename `consumererror.Permanent` to `consumererror.NewPermanent` (#4118) - Rename `config.NewID` to `config.NewComponentID` and `config.NewIDFromString` to `config.NewComponentIDFromString` (#4137) - Rename `config.NewIDWithName` to `config.NewComponentIDWithName` (#4151) - Move `extension/storage` to `extension/experimental/storage` (#4082) - Rename `obsreporttest.SetupRecordedMetricsTest()` to `obsreporttest.SetupTelemetry()` and `obsreporttest.TestTelemetrySettings` to `obsreporttest.TestTelemetry` (#4157) ### 💡 Enhancements 💡 - Add Gen dependabot into CI (#4083) - Update OTLP to v0.10.0 (#4045). - Add Flags field to NumberDataPoint, HistogramDataPoint, SummaryDataPoint (#4081). - Add feature gate library (#4108) - Add version to the internal telemetry metrics (#4140) ### 🧰 Bug fixes 🧰 - Fix panic when not using `service.NewCommand` (#4139) ## v0.36.0 Beta ### 🛑 Breaking changes 🛑 - Remove deprecated pdata.AttributeMapToMap (#3994) - Move ValidateConfig from configcheck to configtest (#3956) - Remove `mem-ballast-size-mib`, already deprecated and no-op (#4005) - Remove `semconv.AttributeMessageType` (#4020) - Remove `semconv.AttributeHTTPStatusText` (#4015) - Remove squash on `configtls.TLSClientSetting` and move TLS client configs under `tls` (#4063) - Rename TLS server config `*configtls.TLSServerSetting` from `tls_settings` to `tls` (#4063) - Split `service.Collector` from the `cobra.Command` (#4074) - Rename `memorylimiter` to `memorylimiterprocessor` (#4064) ### 💡 Enhancements 💡 - Create new semconv package for v1.6.1 (#3948) - Add AttributeValueBytes support to AsString (#4002) - Add AttributeValueTypeBytes support to AttributeMap.AsRaw (#4003) - Add MeterProvider to TelemetrySettings (#4031) - Add configuration to setup collector logs via config file. (#4009) ## v0.35.0 Beta ### 🛑 Breaking changes 🛑 - Remove the legacy gRPC port(`55680`) support in OTLP receiver (#3966) - Rename configparser.Parser to configparser.ConfigMap (#3964) - Remove obsreport.ScraperContext, embed into StartMetricsOp (#3969) - Remove dependency on deprecated go.opentelemetry.io/otel/oteltest (#3979) - Remove deprecated pdata.AttributeValueToString (#3953) - Remove deprecated pdata.TimestampFromTime. Closes: #3925 (#3935) ### 💡 Enhancements 💡 - Add TelemetryCreateSettings (#3984) - Only initialize collector telemetry once (#3918) - Add trace context info to LogRecord log (#3959) - Add new view for AWS ECS health check extension. (#3776) ## v0.34.0 Beta ### 🛑 Breaking changes 🛑 - Artifacts are no longer published in this repository, check [here](https://github.com/open-telemetry/opentelemetry-collector-releases) (#3941) - Remove deprecated `tracetranslator.AttributeValueToString` and `tracetranslator.AttributeMapToMap` (#3873) - Change semantic conventions for status (code, msg) as per specifications (#3872) - Move `fileexporter` to contrib (#3474) - Move `jaegerexporter` to contrib (#3474) - Move `kafkaexporter` to contrib (#3474) - Move `opencensusexporter` to contrib (#3474) - Move `prometheusexporter` to contrib (#3474) - Move `prometheusremotewriteexporter` to contrib (#3474) - Move `zipkinexporter` to contrib (#3474) - Move `attributeprocessor` to contrib (#3474) - Move `filterprocessor` to contrib (#3474) - Move `probabilisticsamplerprocessor` to contrib (#3474) - Move `resourceprocessor` to contrib (#3474) - Move `spanprocessor` to contrib (#3474) - Move `hostmetricsreceiver` to contrib (#3474) - Move `jaegerreceiver` to contrib (#3474) - Move `kafkareceiver` to contrib (#3474) - Move `opencensusreceiver` to contrib (#3474) - Move `prometheusreceiver` to contrib (#3474) - Move `zipkinreceiver` to contrib (#3474) - Move `bearertokenauthextension` to contrib (#3474) - Move `healthcheckextension` to contrib (#3474) - Move `oidcauthextension` to contrib (#3474) - Move `pprofextension` to contrib (#3474) - Move `translator/internaldata` to contrib (#3474) - Move `translator/trace/jaeger` to contrib (#3474) - Move `translator/trace/zipkin` to contrib (#3474) - Move `testbed` to contrib (#3474) - Move `exporter/exporterhelper/resource_to_telemetry` to contrib (#3474) - Move `processor/processorhelper/attraction` to contrib (#3474) - Move `translator/conventions` to `model/semconv` (#3901) ### 🚩 Deprecations 🚩 - Add `pdata.NewTimestampFromTime`, deprecate `pdata.TimestampFromTime` (#3868) - Add `pdata.NewAttributeMapFromMap`, deprecate `pdata.AttributeMap.InitFromMap` (#3936) ## v0.33.0 Beta ### 🛑 Breaking changes 🛑 - Rename `configloader` interface to `configunmarshaler` (#3774) - Remove `LabelsMap` from all the metrics points (#3706) - Update generated K8S attribute labels to fix capitalization (#3823) ### 💡 Enhancements 💡 - Collector has now full support for metrics proto v0.9.0. ## v0.32.0 Beta This release is marked as "bad" since the metrics pipelines will produce bad data. - See https://github.com/open-telemetry/opentelemetry-collector/issues/3824 ### 🛑 Breaking changes 🛑 - Rename `CustomUnmarshable` interface to `Unmarshallable` (#3774) ### 💡 Enhancements 💡 - Change default OTLP/HTTP port number from 55681 to 4318 (#3743) - Update OTLP proto to v0.9.0 (#3740) - Remove `SetValue`/`Value` func for `NumberDataPoint`/`Exemplar` (#3730) - Remove `IntGauge`/`IntSum`from pdata (#3731) - Remove `IntDataPoint` from pdata (#3735) - Add support for `Bytes` attribute type (#3756) - Add `SchemaUrl` field (#3759) - Add `Attributes` to `NumberDataPoint`, `HistogramDataPoint`, `SummaryDataPoint` (#3761) - `conventions` translator: Replace with conventions generated from spec v1.5.0 (#3494) - `prometheus` receiver: Add `ToMetricPdata` method (#3695) - Make configsource `Watchable` an optional interface (#3792) - `obsreport` exporter: Change to accept `ExporterCreateSettings` (#3789) ### 🧰 Bug fixes 🧰 - `configgrpc`: Use chained interceptors in the gRPC server (#3744) - `prometheus` receiver: Use actual interval startTimeMs for cumulative types (#3694) - `jaeger` translator: Fix bug that could generate empty proto spans (#3808) ## v0.31.0 Beta ### 🛑 Breaking changes 🛑 - Remove Resize() from pdata slice APIs (#3675) - Remove the ballast allocation when `mem-ballast-size-mib` is set in command line (#3626) - Use `ballast extension` to set memory ballast instead. - Rename `DoubleDataPoint` to `NumberDataPoint` (#3633) - Remove `IntHistogram` (#3676) ### 💡 Enhancements 💡 - Update to OTLP 0.8.0: - Translate `IntHistogram` to `Histogram` in `otlp_wrappers` (#3676) - Translate `IntGauge` to `Gauge` in `otlp_wrappers` (#3619) - Translate `IntSum` to `Sum` in `otlp_wrappers` (#3621) - Update `NumberDataPoint` to support `DoubleVal` and `IntVal` (#3689) - Update `Exemplar` to use `oneOfPrimitiveValue` (#3699) - Remove `IntExemplar` and `IntExemplarSlice` from `pdata` (#3705) - Mark `IntGauge`/`IntSum`/`IntDataPoint` as deprecated (#3707) - Remove `IntGauge`/`IntSum` from `batchprocessor` (#3718) - `prometheusremotewrite` exporter: Convert to new Number metrics (#3714) - `prometheus` receiver: Convert to new Number metrics (#3716) - `prometheus` exporter: Convert to new Number metrics (#3709) - `hostmetrics` receiver: Convert to new Number metrics (#3710) - `opencensus`: Convert to new Number metrics (#3708) - `scraperhelper` receiver: Convert to new Number metrics (#3717) - `testbed`: Convert to new Number metrics (#3719) - `exporterhelper`: Convert `resourcetolabel` to new Number metrics (#3723) - `configauth`: Prepare auth API to return a context (#3618) - `pdata`: - Implement `Equal()` for map-valued `AttributeValues` (#3612) - Add `[Type]Slice.Sort(func)` to sort slices (#3671) - `memorylimiter`: - Add validation on ballast size between `memorylimiter` and `ballastextension` (#3532) - Access Ballast extension via `Host.GetExtensions` (#3634) - `prometheusremotewrite` exporter: Add a WAL implementation without wiring up (#3597) - `prometheus` receiver: Add `metricGroup.toDistributionPoint` pdata conversion (#3667) - Use `ComponentID` as identifier instead of config (#3696) - `zpages`: Move config validation from factory to `Validate` (#3697) - Enable `tracez` z-pages from otel-go, disable opencensus (#3698) - Convert temporality and monotonicity for deprecated sums (#3729) ### 🧰 Bug fixes 🧰 - `otlpexporter`: Allow endpoint to be configured with a scheme of `http` or `https` (#3575) - Handle errors when reloading the collector service (#3615) - Do not report fatal error when `cmux.ErrServerClosed` (#3703) - Fix bool attribute equality in `pdata` (#3688) ## v0.30.0 Beta ### 🛑 Breaking changes 🛑 - Rename `pdata.DoubleSum` to `pdata.Sum` (#3583) - Rename `pdata.DoubleGauge` to `pdata.Gauge` (#3599) - Migrated `pdata` to a dedicated package (#3483) - Change Marshaler/Unmarshaler to be consistent with other interfaces (#3502) - Remove consumer/simple package (#3438) - Remove unnecessary interfaces from pdata (#3506) - zipkinv1 implement directly Unmarshaler interface (#3504) - zipkinv2 implement directly Marshaler/Unmarshaler interface (#3505) - Change exporterhelper to accept ExporterCreateSettings instead of just logger (#3569) - Use Func pattern in processorhelper, consistent with others (#3570) ### 🚩 Deprecations 🚩 - Deprecate Resize() from pdata slice APIs (#3573) ### 💡 Enhancements 💡 - Update OTLP to v0.8.0 (#3572) - Migrate from OpenCensus to OpenTelemetry for internal tracing (#3567) - Move internal/pdatagrpc to model/otlpgrpc (#3507) - Move internal/otlp to model/otlp (#3508) - Create http Server via Config, enable cors and decompression (#3513) - Allow users to set min and max TLS versions (#3591) - Support setting ballast size in percentage of total Mem in ballast extension (#3456) - Publish go.opentelemetry.io/collector/model as a separate module (#3530) - Pass a TracerProvider via construct settings to all the components (#3592) - Make graceful shutdown optional (#3577) ### 🧰 Bug fixes 🧰 - `scraperhelper`: Include the scraper name in log messages (#3487) - `scraperhelper`: fix case when returned pdata is empty (#3520) - Record the correct number of points not metrics in Kafka receiver (#3553) - Validate the Prometheus configuration (#3589) ## v0.29.0 Beta ### 🛑 Breaking changes 🛑 - Rename `service.Application` to `service.Collector` (#3268) - Provide case sensitivity in config yaml mappings by using Koanf instead of Viper (#3337) - Move zipkin constants to an internal package (#3431) - Disallow renaming metrics using metric relabel configs (#3410) - Move cgroup and iruntime utils from memory_limiter to internal folder (#3448) - Move model pdata interfaces to pdata, expose them publicly (#3455) ### 💡 Enhancements 💡 - Change obsreport helpers for scraper to use the same pattern as Processor/Exporter (#3327) - Convert `otlptext` to implement Marshaler interfaces (#3366) - Add encoder/decoder and marshaler/unmarshaler for OTLP protobuf (#3401) - Use the new marshaler/unmarshaler in `kafka` exporter (#3403) - Convert `zipkinv2` to to/from translator interfaces (#3409) - `zipkinv1`: Move to translator and encoders interfaces (#3419) - Use the new marshaler/unmarshaler in `kafka` receiver #3402 - Change `oltp` receiver to use the new unmarshaler, avoid grpc-gateway dependency (#3406) - Use the new Marshaler in the `otlphttp` exporter (#3433) - Add grpc response struct for all signals instead of returning interface in `otlp` receiver/exporter (#3437) - `zipkinv2`: Add encoders, decoders, marshalers (#3426) - `scrapererror` receiver: Return concrete error type (#3360) - `kafka` receiver: Add metrics support (#3452) - `prometheus` receiver: - Add store to track stale metrics (#3414) - Add `up` and `scrape_xxxx` internal metrics (#3116) ### 🧰 Bug fixes 🧰 - `prometheus` receiver: - Reject datapoints with duplicate label keys (#3408) - Scrapers are not stopped when receiver is shutdown (#3450) - `prometheusremotewrite` exporter: Adjust default retry settings (#3416) - `hostmetrics` receiver: Fix missing startTimestamp for `processes` scraper (#3461) ## v0.28.0 Beta ### 🛑 Breaking changes 🛑 - Remove unused logstest package (#3222) - Introduce `AppSettings` instead of `Parameters` (#3163) - Remove unused testutil.TempSocketName (#3291) - Move BigEndian helper functions in `tracetranslator` to an internal package.(#3298) - Rename `configtest.LoadConfigFile` to `configtest.LoadConfigAndValidate` (#3306) - Replace `ExtensionCreateParams` with `ExtensionCreateSettings` (#3294) - Replace `ProcessorCreateParams` with `ProcessorCreateSettings`. (#3181) - Replace `ExporterCreateParams` with `ExporterCreateSettings` (#3164) - Replace `ReceiverCreateParams` with `ReceiverCreateSettings`. (#3167) - Change `batchprocessor` logic to limit data points rather than metrics (#3141) - Rename `PrwExporter` to `PRWExporter` and `NewPrwExporter` to `NewPRWExporter` (#3246) - Avoid exposing OpenCensus reference in public APIs (#3253) - Move `config.Parser` to `configparser.Parser` (#3304) - Remove deprecated funcs inside the obsreceiver (#3314) - Remove `obsreport.GRPCServerWithObservabilityEnabled`, enable observability in config (#3315) - Remove `obsreport.ProcessorMetricViews`, use `BuildProcessorCustomMetricName` where needed (#3316) - Remove "Receive" from `obsreport.Receiver` funcs (#3326) - Remove "Export" from `obsreport.Exporter` funcs (#3333) - Hide unnecessary public struct `obsreport.StartReceiveOptions` (#3353) - Avoid exposing internal implementation public in OC/OTEL receivers (#3355) - Updated configgrpc `ToDialOptions` and confighttp `ToClient` apis to take extensions configuration map (#3340) - Remove `GenerateSequentialTraceID` and `GenerateSequentialSpanIDin` functions in testbed (#3390) - Change "grpc" to "GRPC" in configauth function/type names (#3285) ### 💡 Enhancements 💡 - Add `doc.go` files to the consumer package and its subpackages (#3270) - Improve documentation of consumer package and subpackages (#3269, #3361) - Automate triggering of doc-update on release (#3234) - Enable Dependabot for Github Actions (#3312) - Remove the proto dependency in `goldendataset` for traces (#3322) - Add telemetry for dropped data due to exporter sending queue overflow (#3328) - Add initial implementation of `pdatagrpc` (#3231) - Change receiver obsreport helpers pattern to match the Processor/Exporter (#3227) - Add model translation and encoding interfaces (#3200) - Add otlpjson as a serializer implementation (#3238) - `prometheus` receiver: - Add `createNodeAndResourcePdata` for Prometheus->OTLP pdata (#3139) - Direct metricfamily Prometheus->OTLP (#3145) - Add `componenttest.NewNop*CreateSettings` to simplify tests (#3375) - Add support for markdown generation (#3100) - Refactor components for the Client Authentication Extensions (#3287) ### 🧰 Bug fixes 🧰 - Use dedicated `zapcore.Core` for Windows service (#3147) - Hook up start and shutdown functions in fileexporter (#3260) - Fix oc to pdata translation for sum non-monotonic cumulative (#3272) - Fix `timeseriesSignature` in prometheus receiver (#3310) ## v0.27.0 Beta ### 🛑 Breaking changes 🛑 - Change `Marshal` signatures in kafkaexporter's Marshalers to directly convert pdata to `sarama.ProducerMessage` (#3162) - Remove `tracetranslator.DetermineValueType`, only used internally by Zipkin (#3114) - Remove OpenCensus conventions, should not be used (#3113) - Remove Zipkin specific translation constants, move to internal (#3112) - Remove `tracetranslator.TagHTTPStatusCode`, use `conventions.AttributeHTTPStatusCode` (#3111) - Remove OpenCensus status constants and transformation (#3110) - Remove `tracetranslator.AttributeArrayToSlice`, not used in core or contrib (#3109) - Remove `internaldata.MetricsData`, same APIs as for traces (#3156) - Rename `config.IDFromString` to `NewIDFromString`, remove `MustIDFromString` (#3177) - Move consumerfanout package to internal (#3207) - Canonicalize enum names in pdata. Fix usage of uppercase names (#3208) ### 💡 Enhancements 💡 - Use `config.ComponentID` for obsreport receiver/scraper (#3098) - Add initial implementation of the consumerhelper (#3146) - Add Collector version to Prometheus Remote Write Exporter user-agent header (#3094) - Refactor processorhelper to use consumerhelper, split by signal type (#3180) - Use consumerhelper for exporterhelper, add WithCapabilities (#3186) - Set capabilities for all core exporters, remove unnecessary funcs (#3190) - Add an internal sharedcomponent to be shared by receivers with shared resources (#3198) - Allow users to configure the Prometheus remote write queue (#3046) - Mark internaldata traces translation as deprecated for external usage (#3176) ### 🧰 Bug fixes 🧰 - Fix Prometheus receiver metric start time and reset determination logic. (#3047) - The receiver will no longer drop the first sample for `counter`, `summary`, and `histogram` metrics. - The Prometheus remote write exporter will no longer force `counter` metrics to have a `_total` suffix. (#2993) - Remove locking from jaeger receiver start and stop processes (#3070) - Fix batch processor metrics reorder, improve performance (#3034) - Fix batch processor traces reorder, improve performance (#3107) - Fix batch processor logs reorder, improve performance (#3125) - Avoid one unnecessary allocation in grpc OTLP exporter (#3122) - `batch` processor: Validate that batch config max size is greater than send size (#3126) - Add capabilities to consumer, remove from processor (#2770) - Remove internal protos usage in Prometheusremotewrite exporter (#3184) - `prometheus` receiver: Honor Prometheus external labels (#3127) - Validate that remote write queue settings are not negative (#3213) ## v0.26.0 Beta ### 🛑 Breaking changes 🛑 - Change `With*Unmarshallers` signatures in Kafka exporter/receiver (#2973) - Rename `marshall` to `marshal` in all the occurrences (#2977) - Remove `componenterror.ErrAlreadyStarted` and `componenterror.ErrAlreadyStopped`, components should not protect against this, Service will start/stop once. - Rename `ApplicationStartInfo` to `BuildInfo` - Rename `ApplicationStartInfo.ExeName` to `BuildInfo.Command` - Rename `ApplicationStartInfo.LongName` to `BuildInfo.Description` ### 🚩 Deprecations 🚩 - Add AppendEmpty and deprecate Append for slices (#2970) ### 💡 Enhancements 💡 - `kafka` exporter: Add logs support (#2943) - Update mdatagen to create factories of init instead of new (#2978) - `zipkin` receiver: Reduce the judgment of zipkin v1 version (#2990) - Custom authenticator logic to accept a `component.Host` which will extract the authenticator to use based on a new authenticator name property (#2767) - `prometheusremotewrite` exporter: Add `resource_to_telemetry_conversion` config option (#3031) - `logging` exporter: Extract OTLP text logging (#3082) - Format timestamps as strings instead of int in otlptext output (#3088) - Add darwin arm64 build (#3090) ### 🧰 Bug fixes 🧰 - Fix Jaeger receiver to honor TLS Settings (#2866) - `zipkin` translator: Handle missing starttime case for zipkin json v2 format spans (#2506) - `prometheus` exporter: Fix OTEL resource label drops (#2899) - `prometheusremotewrite` exporter: - Enable the queue internally (#2974) - Don't drop instance and job labels (#2979) - `jaeger` receiver: Wait for server goroutines exit on shutdown (#2985) - `logging` exporter: Ignore invalid handle on close (#2994) - Fix service zpages (#2996) - `batch` processor: Fix to avoid reordering and send max size (#3029) ## v0.25.0 Beta ### 🛑 Breaking changes 🛑 - Rename ForEach (in pdata) with Range to be consistent with sync.Map (#2931) - Rename `componenthelper.Start` to `componenthelper.StartFunc` (#2880) - Rename `componenthelper.Stop` to `componenthelper.StopFunc` (#2880) - Remove `exporterhelper.WithCustomUnmarshaler`, `processorhelper.WithCustomUnmarshaler`, `receiverhelper.WithCustomUnmarshaler`, `extensionhelper.WithCustomUnmarshaler`, implement `config.CustomUnmarshaler` interface instead (#2867) - Remove `component.CustomUnmarshaler` implement `config.CustomUnmarshaler` interface instead (#2867) - Remove `testutil.HostPortFromAddr`, users can write their own parsing helper (#2919) - Remove `configparser.DecodeTypeAndName`, use `config.IDFromString` (#2869) - Remove `config.NewViper`, users should use `config.NewParser` (#2917) - Remove `testutil.WaitFor`, use `testify.Eventually` helper if needed (#2920) - Remove testutil.WaitForPort, users can use testify.Eventually (#2926) - Rename `processorhelper.NewTraceProcessor` to `processorhelper.NewTracesProcessor` (#2935) - Rename `exporterhelper.NewTraceExporter` to `exporterhelper.NewTracesExporter` (#2937) - Remove InitEmptyWithCapacity, add EnsureCapacity and Clear (#2845) - Rename traces methods/objects to include Traces in Kafka receiver (#2966) ### 💡 Enhancements 💡 - Add `validatable` interface with `Validate()` to all `config.` (#2898) - add the empty `Validate()` implementation for all component configs - **Experimental**: Add a config source manager that wraps the interaction with config sources (#2857, #2903, #2948) - `kafka` exporter: Key jaeger messages on traceid (#2855) - `scraperhelper`: Don't try to count metrics if scraper returns an error (#2902) - Extract ConfigFactory in a ParserProvider interface (#2868) - `prometheus` exporter: Allows Summary metrics to be exported to Prometheus (#2900) - `prometheus` receiver: Optimize `dpgSignature` function (#2945) - `kafka` receiver: Add logs support (#2944) ### 🧰 Bug fixes 🧰 - `prometheus` receiver: - Treat Summary and Histogram metrics without "\_sum" counter as valid metric (#2812) - Add `job` and `instance` as well-known labels (#2897) - `prometheusremotewrite` exporter: - Sort Sample by Timestamp to avoid out of order errors (#2941) - Remove incompatible queued retry (#2951) - `kafka` receiver: Fix data race with batchprocessor (#2957) - `jaeger` receiver: Jaeger agent should not report ErrServerClosed (#2965) ## v0.24.0 Beta ### 🛑 Breaking changes 🛑 - Remove legacy internal metrics for memorylimiter processor, `spans_dropped` and `trace_batches_dropped` (#2841) - For `spans_dropped` use `processor/refused_spans` with `processor=memorylimiter` - Rename pdata._.[Start|End]Time to pdata._.[Start|End]Timestamp (#2847) - Rename pdata.DoubleExemplar to pdata.Exemplar (#2804) - Rename pdata.DoubleHistogram to pdata.Histogram (#2797) - Rename pdata.DoubleSummary to pdata.Summary (#2774) - Refactor `consumererror` package (#2768) - Remove `PartialError` type in favor of signal-specific types - Rename `CombineErrors()` to `Combine()` - Refactor `componenthelper` package (#2778) - Remove `ComponentSettings` and `DefaultComponentSettings()` - Rename `NewComponent()` to `New()` - obsReport.NewExporter accepts a settings struct (#2668) - Remove ErrorWaitingHost from `componenttest` (#2582) - Move `config.Load` to `configparser.Load` (#2796) - Remove `configtest.NewViperFromYamlFile()`, use `config.Parser.NewParserFromFile()` (#2806) - Remove `config.ViperSubExact()`, use `config.Parser.Sub()` (#2806) - Update LoadReceiver signature to remove unused params (#2823) - Move `configerror.ErrDataTypeIsNotSupported` to `componenterror.ErrDataTypeIsNotSupported` (#2886) - Rename`CreateTraceExporter` type to `CreateTracesExporter` in `exporterhelper` (#2779) - Move `fluentbit` extension to contrib (#2795) - Move `configmodels` to `config` (#2808) - Move `fluentforward` receiver to contrib (#2723) ### 🚩 Deprecations 🚩 - Deprecate `consumetest.New[${SIGNAL}]Nop` in favor of `consumetest.NewNop` (#2878) - Deprecate `consumetest.New[${SIGNAL}]Err` in favor of `consumetest.NewErr` (#2878) ### 💡 Enhancements 💡 - `batch` processor: - Support max batch size for logs (#2736) - Use `Endpoint` for health check extension (#2782) - Use `confignet.TCPAddr` for `pprof` and `zpages` extensions (#2829) - Add watcher to values retrieved via config sources (#2803) - Updates for cloud semantic conventions (#2809) - `cloud.infrastructure_service` -> `cloud.platform` - `cloud.zone` -> `cloud.availability_zone` - Add systemd environment file for deb/rpm packages (#2822) - Add validate interface in `configmodels` to force each component do configuration validation (#2802, #2856) - Add `aws.ecs.task.revision` to semantic conventions list (#2816) - Set unprivileged user to container image (#2838) - Add New funcs for extension, exporter, processor config settings (#2872) - Report metric about current size of the exporter retry queue (#2858) - Allow adding new signals in `ProcessorFactory` by forcing everyone to embed `BaseProcessorFactory` (#2885) ### 🧰 Bug fixes 🧰 - `pdata.TracesFromOtlpProtoBytes`: Fixes to handle backwards compatibility changes in proto (#2798) - `jaeger` receiver: Escape user input used in output (#2815) - `prometheus` exporter: Ensure same time is used for updated time (#2745) - `prometheusremotewrite` exporter: Close HTTP response body (#2875) ## v0.23.0 Beta ### 🛑 Breaking changes 🛑 - Move fanout consumers to fanoutconsumer package (#2615) - Rename ExporterObsReport to Exporter (#2658) - Rename ProcessorObsReport to Processor (#2657) - Remove ValidateConfig and add Validate on the Config struct (#2665) - Rename pdata Size to OtlpProtoSize (#2726) - Rename [Traces|Metrics|Logs]Consumer to [Traces|Metrics|Logs] (#2761) - Remove public access for `componenttest.Example*` components: - Users of these structs for testing configs should use the newly added `componenttest.Nop*` (update all components name in the config `example*` -> `nop` and use `componenttest.NopComponents()`). - Users of these structs for sink like behavior should use `consumertest.*Sink`. ### 💡 Enhancements 💡 - `hostmetrics` receiver: List labels along with respective metrics in metadata (#2662) - `exporter` helper: Remove obsreport.ExporterContext, always add exporter name as a tag to the metrics (#2682) - `jaeger` exporter: Change to not use internal data (#2698) - `kafka` receiver: Change to not use internal data (#2697) - `zipkin` receiver: Change to not use internal data (#2699) - `kafka` exporter: Change to not use internal data (#2696) - Ensure that extensions can be created and started multiple times (#2679) - Use otlp request in logs wrapper, hide members in the wrapper (#2692) - Add MetricsWrapper to disallow access to internal representation (#2693) - Add TracesWrapper to disallow access to internal representation (#2721) - Allow multiple OTLP receivers to be created (#2743) ### 🧰 Bug fixes 🧰 - `prometheus` exporter: Fix to work with standard labels that follow the naming convention of using periods instead of underscores (#2707) - Propagate name and transport for `prometheus` receiver and exporter (#2680) - `zipkin` receiver: Ensure shutdown correctness (#2765) ## v0.22.0 Beta ### 🛑 Breaking changes 🛑 - Rename ServiceExtension to just Extension (#2581) - Remove `consumerdata.TraceData` (#2551) - Move `consumerdata.MetricsData` to `internaldata.MetricsData` (#2512) - Remove custom OpenCensus sematic conventions that have equivalent in otel (#2552) - Move ScrapeErrors and PartialScrapeError to `scrapererror` (#2580) - Remove support for deprecated unmarshaler `CustomUnmarshaler`, only `Unmarshal` is supported (#2591) - Remove deprecated componenterror.CombineErrors (#2598) - Rename `pdata.TimestampUnixNanos` to `pdata.Timestamp` (#2549) ### 💡 Enhancements 💡 - `prometheus` exporter: Re-implement on top of `github.com/prometheus/client_golang/prometheus` and add `metric_expiration` option - `logging` exporter: Add support for AttributeMap (#2609) - Add semantic conventions for instrumentation library (#2602) ### 🧰 Bug fixes 🧰 - `otlp` receiver: Fix `Shutdown()` bug (#2564) - `batch` processor: Fix Shutdown behavior (#2537) - `logging` exporter: Fix handling the loop for empty attributes (#2610) - `prometheusremotewrite` exporter: Fix counter name check (#2613) ## v0.21.0 Beta ### 🛑 Breaking changes 🛑 - Remove deprecated function `IsValid` from trace/span ID (#2522) - Remove accessors for deprecated status code (#2521) ### 💡 Enhancements 💡 - `otlphttp` exporter: Add `compression` option for gzip encoding of outgoing http requests (#2502) - Add `ScrapeErrors` struct to `consumererror` to simplify errors usage (#2414) - Add `cors_allowed_headers` option to `confighttp` (#2454) - Add SASL/SCRAM authentication mechanism on `kafka` receiver and exporter (#2503) ### 🧰 Bug fixes 🧰 - `otlp` receiver: Sets the correct deprecated status code before sending data to the pipeline (#2521) - Fix `IsPermanent` to account for wrapped errors (#2455) - `otlp` exporter: Preserve original error messages (#2459) ## v0.20.0 Beta ### 🛑 Breaking changes 🛑 - Rename `samplingprocessor/probabilisticsamplerprocessor` to `probabilisticsamplerprocessor` (#2392) ### 💡 Enhancements 💡 - `hostmetrics` receiver: Refactor to use metrics metadata utilities (#2405, #2406, #2421) - Add k8s.node semantic conventions (#2425) ## v0.19.0 Beta ### 🛑 Breaking changes 🛑 - Remove deprecated `queued_retry` processor - Remove deprecated configs from `resource` processor: `type` (set "opencensus.type" key in "attributes.upsert" map instead) and `labels` (use "attributes.upsert" instead). ### 💡 Enhancements 💡 - `hostmetrics` receiver: Refactor load metrics to use generated metrics (#2375) - Add uptime to the servicez debug page (#2385) - Add new semantic conventions for AWS (#2365) ### 🧰 Bug fixes 🧰 - `jaeger` exporter: Improve connection state logging (#2239) - `pdatagen`: Fix slice of values generated code (#2403) - `filterset` processor: Avoid returning always nil error in strict filterset (#2399) ## v0.18.0 Beta ### 🛑 Breaking changes 🛑 - Rename host metrics according to metrics spec and rename `swap` scraper to `paging` (#2311) ### 💡 Enhancements 💡 - Add check for `NO_WINDOWS_SERVICE` environment variable to force interactive mode on Windows (#2272) - `hostmetrics` receiver: Add `disk/weighted_io_time` metric (Linux only) (#2312) - `opencensus` exporter: Add queue-retry (#2307) - `filter` processor: Filter metrics using resource attributes (#2251) ### 🧰 Bug fixes 🧰 - `fluentforward` receiver: Fix string conversions (#2314) - Fix zipkinv2 translation error tag handling (#2253) ## v0.17.0 Beta ### 💡 Enhancements 💡 - Default config environment variable expansion (#2231) - `prometheusremotewrite` exporter: Add batched exports (#2249) - `memorylimiter` processor: Introduce soft and hard limits (#2250) ### 🧰 Bug fixes 🧰 - Fix nits in pdata usage (#2235) - Convert status to not be a pointer in the Span (#2242) - Report the error from `pprof.StartCPUProfile` (#2263) - Rename `service.Application.SignalTestComplete` to `Shutdown` (#2277) ## v0.16.0 Beta ### 🛑 Breaking changes 🛑 - Rename Push functions to be consistent across signals in `exporterhelper` (#2203) ### 💡 Enhancements 💡 - Change default OTLP/gRPC port number to 4317, also continue receiving on legacy port 55680 during transition period (#2104). - `kafka` exporter: Add support for exporting metrics as otlp Protobuf. (#1966) - Move scraper helpers to its own `scraperhelper` package (#2185) - Add `componenthelper` package to help build components (#2186) - Remove usage of custom init/stop in `scraper` and use start/shutdown from `component` (#2193) - Add more trace annotations, so zpages are more useful to determine failures (#2206) - Add support to skip TLS verification (#2202) - Expose non-nullable metric types (#2208) - Expose non-nullable elements from slices of pointers (#2200) ### 🧰 Bug fixes 🧰 - Change InstrumentationLibrary to be non-nullable (#2196) - Add support for slices to non-pointers, use non-nullable AnyValue (#2192) - Fix `--set` flag to work with `{}` in configs (#2162) ## v0.15.0 Beta ### 🛑 Breaking changes 🛑 - Remove legacy metrics, they were marked as legacy for ~12 months #2105 ### 💡 Enhancements 💡 - Implement conversion between OpenCensus and OpenTelemetry Summary Metric (#2048) - Add ability to generate non nullable messages (#2005) - Implement Summary Metric in Prometheus RemoteWrite Exporter (#2083) - Add `resource_to_telemetry_conversion` to exporter helper expose exporter settings (#2060) - Add `CustomRoundTripper` function to httpclientconfig (#2085) - Allow for more logging options to be passed to `service` (#2132) - Add config parameters for `jaeger` receiver (#2068) - Map unset status code for `jaeger` translator as per spec (#2134) - Add more trace annotations to the queue-retry logic (#2136) - Add config settings for component telemetry (#2148) - Use net.SplitHostPort for IPv6 support in `prometheus` receiver (#2154) - Add --log-format command line option (default to "console") #2177. ### 🧰 Bug fixes 🧰 - `logging` exporter: Add Logging for Summary Datapoint (#2084) - `hostmetrics` receiver: use correct TCP state labels on Unix systems (#2087) - Fix otlp_log receiver wrong use of trace measurement (#2117) - Fix "process/memory/rss" metric units (#2112) - Fix "process/cpu_seconds" metrics (#2113) - Add check for nil logger in exporterhelper functions (#2141) - `prometheus` receiver: - Upgrade Prometheus version to fix race condition (#2121) - Fix the scraper/discover manager coordination (#2089) - Fix panic when adjusting buckets (#2168) ## v0.14.0 Beta ### 🚀 New components 🚀 - `otlphttp` exporter which implements OTLP over HTTP protocol. ### 🛑 Breaking changes 🛑 - Rename consumer.TraceConsumer to consumer.TracesConsumer #1974 - Rename component.TraceReceiver to component.TracesReceiver #1975 - Rename component.TraceProcessor to component.TracesProcessor #1976 - Rename component.TraceExporter to component.TracesExporter #1975 - Move `tailsampling` processor to contrib (#2012) - Remove NewAttributeValueSlice (#2028) and mark NewAttributeValue as deprecated (#2022) - Remove pdata.StringValue (#2021) - Remove pdata.InitFromAttributeMap, use CopyTo if needed (#2042) - Remove SetMapVal and SetArrayVal for pdata.AttributeValue (#2039) ### 🚩 Deprecations 🚩 - Deprecate NopExporter, add NopConsumer (#1972) - Deprecate SinkExporter, add SinkConsumer (#1973) ### 💡 Enhancements 💡 - `zipkin` exporter: Add queue retry to zipkin (#1971) - `prometheus` exporter: Add `send_timestamps` option (#1951) - `filter` processor: Add `expr` pdata.Metric filtering support (#1940, #1996) - `attribute` processor: Add log support (#1934) - `logging` exporter: Add index for histogram buckets count (#2009) - `otlphttp` exporter: Add correct handling of server error responses (#2016) - `prometheusremotewrite` exporter: - Add user agent header to outgoing http request (#2000) - Convert histograms to cumulative (#2049) - Return permanent errors (#2053) - Add external labels (#2044) - `hostmetrics` receiver: Use scraper controller (#1949) - Change Span/Trace ID to be byte array (#2001) - Add `simple` metrics helper to facilitate building pdata.Metrics in receivers (#1540) - Improve diagnostic logging for exporters (#2020) - Add obsreport to receiverhelper scrapers (#1961) - Update OTLP to 0.6.0 and use the new Span Status code (#2031) - Add support of partial requests for logs and metrics to the exporterhelper (#2059) ### 🧰 Bug fixes 🧰 - `logging` exporter: Added array serialization (#1994) - `zipkin` receiver: Allow receiver to parse string tags (#1893) - `batch` processor: Fix shutdown race (#1967) - Guard for nil data points (#2055) ## v0.13.0 Beta ### 🛑 Breaking changes 🛑 - Host metric `system.disk.time` renamed to `system.disk.operation_time` (#1887) - Use consumer for sender interface, remove unnecessary receiver address from Runner (#1941) - Enable sending queue by default in all exporters configured to use it (#1924) - Removed `groupbytraceprocessor` (#1891) - Remove ability to configure collection interval per scraper (#1947) ### 💡 Enhancements 💡 - Host Metrics receiver now reports both `system.disk.io_time` and `system.disk.operation_time` (#1887) - Match spans against the instrumentation library and resource attributes (#928) - Add `receiverhelper` for creating flexible "scraper" metrics receiver (#1886, #1890, #1945, #1946) - Migrate `tailsampling` processor to new OTLP-based internal data model and add Composite Sampler (#1894) - Metadata Generator: Change Metrics fields to implement an interface with new methods (#1912) - Add unmarshalling for `pdata.Traces` (#1948) - Add debug-level message on error for `jaeger` exporter (#1964) ### 🧰 Bug fixes 🧰 - Fix bug where the service does not correctly start/stop the log exporters (#1943) - Fix Queued Retry Unusable without Batch Processor (#1813) - (#1930) - `prometheus` receiver: Log error message when `process_start_time_seconds` gauge is missing (#1921) - Fix trace jaeger conversion to internal traces zero time bug (#1957) - Fix panic in otlp traces to zipkin (#1963) - Fix OTLP/HTTP receiver's path to be /v1/traces (#1979) ## v0.12.0 Beta ### 🚀 New components 🚀 - `configauth` package with the auth settings that can be used by receivers (#1807, #1808, #1809, #1810) - `perfcounters` package that uses perflib for host metrics receiver (#1835, #1836, #1868, #1869, #1870) ### 💡 Enhancements 💡 - Remove `queued_retry` and enable `otlp` metrics receiver in default config (#1823, #1838) - Add `limit_percentage` and `spike_limit_percentage` options to `memorylimiter` processor (#1622) - `hostmetrics` receiver: - Collect additional labels from partitions in the filesystems scraper (#1858) - Add filters for mount point and filesystem type (#1866) - Add cloud.provider semantic conventions (#1865) - `attribute` processor: Add log support (#1783) - Introduce SpanID data type, not yet used in Protobuf messages ($1854, #1855) - Enable `otlp` trace by default in the released docker image (#1883) - `tailsampling` processor: Combine batches of spans into a single batch (#1864) - `filter` processor: Update to use pdata (#1885) - Allow MSI upgrades (#1914) ### 🚩 Deprecations 🚩 - Deprecate OpenCensus-based internal data structures (#1843) ### 🧰 Bug fixes 🧰 - `prometheus` receiver: Print a more informative message about 'up' metric value (#1826) - Use custom data type and custom JSON serialization for traceid (#1840) - Skip creation of redundant nil resource in translation from OC if there are no combined metrics (#1803) - `tailsampling` processor: Only send to next consumer once (#1735) - Report Windows pagefile usage in bytes (#1837) - Fix issue where Prometheus SD config cannot be parsed (#1877) ## v0.11.0 Beta ### 🛑 Breaking changes 🛑 - Rename service.Start() to Run() since it's a blocking call - Fix slice Append to accept by value the element in pdata - Change CreateTraceProcessor and CreateMetricsProcessor to use the same parameter order as receivers/logs processor and exporters. - Prevent accidental use of LogsToOtlp and LogsFromOtlp and the OTLP data structs (#1703) - Remove SetType from configmodels, ensure all registered factories set the type in config (#1798) - Move process telemetry to service/internal (#1794) ### 💡 Enhancements 💡 - Add map and array attribute value type support (#1656) - Add authentication support to kafka (#1632) - Implement InstrumentationLibrary translation to jaeger (#1645) - Add public functions to export pdata to ExportXServicesRequest Protobuf bytes (#1741) - Expose telemetry level in the configtelemetry (#1796) - Add configauth package (#1807) - Add config to docker image (#1792) ### 🧰 Bug fixes 🧰 - Use zap int argument for int values instead of conversion (#1779) - Add support for gzip encoded payload in OTLP/HTTP receiver (#1581) - Return proto status for OTLP receiver when failed (#1788) ## v0.10.0 Beta ### 🛑 Breaking changes 🛑 - **Update OTLP to v0.5.0, incompatible metrics protocol.** - Remove support for propagating summary metrics in OtelCollector. - This is a temporary change, and will affect mostly OpenCensus users who use metrics. ### 💡 Enhancements 💡 - Support zipkin proto in `kafka` receiver (#1646) - Prometheus Remote Write Exporter supporting Cortex (#1577, #1643) - Add deployment environment semantic convention (#1722) - Add logs support to `batch` and `resource` processors (#1723, #1729) ### 🧰 Bug fixes 🧰 - Identify config error when expected map is other value type (#1641) - Fix Kafka receiver closing ready channel multiple times (#1696) - Fix a panic issue while processing Zipkin spans with an empty service name (#1742) - Zipkin Receiver: Always set the endtime (#1750) ## v0.9.0 Beta ### 🛑 Breaking changes 🛑 - **Remove old base factories**: - `ReceiverFactoryBase` (#1583) - `ProcessorFactoryBase` (#1596) - `ExporterFactoryBase` (#1630) - Remove logs factories and merge with normal factories (#1569) - Remove `reconnection_delay` from OpenCensus exporter (#1516) - Remove `ConsumerOld` interfaces (#1631) ### 🚀 New components 🚀 - `prometheusremotewrite` exporter: Send metrics data in Prometheus TimeSeries format to Cortex or any Prometheus (#1544) - `kafka` receiver: Receive traces from Kafka (#1410) ### 💡 Enhancements 💡 - `kafka` exporter: Enable queueing, retry, timeout (#1455) - Add `Headers` field in HTTPClientSettings (#1552) - Change OpenCensus receiver (#1556) and exporter (#1571) to the new interfaces - Add semantic attribute for `telemetry.auto.version` (#1578) - Add uptime and RSS memory self-observability metrics (#1549) - Support conversion for OpenCensus `SameProcessAsParentSpan` (#1629) - Access application version in components (#1559) - Make Kafka payload encoding configurable (#1584) ### 🧰 Bug fixes 🧰 - Stop further processing if `filterprocessor` filters all data (#1500) - `processscraper`: Use same scrape time for all data points coming from same process (#1539) - Ensure that time conversion for 0 returns nil timestamps or Time where IsZero returns true (#1550) - Fix multiple exporters panic (#1563) - Allow `attribute` processor for external use (#1574) - Do not duplicate filesystem metrics for devices with many mount points (#1617) ## v0.8.0 Beta ### 🚀 New components 🚀 - `groupbytrace` processor that waits for a trace to be completed (#1362) ### 💡 Enhancements 💡 - Migrate `zipkin` receiver/exporter to the new interfaces (#1484) - Migrate `prometheus` receiver/exporter to the new interfaces (#1477, #1515) - Add new FactoryUnmarshaler support to all components, deprecate old way (#1468) - Update `fileexporter` to write data in OTLP (#1488) - Add extension factory helper (#1485) - Host scrapers: Use same scrape time for all data points coming from same source (#1473) - Make logs SeverityNumber publicly available (#1496) - Add recently included conventions for k8s and container resources (#1519) - Add new config StartTimeMetricRegex to `prometheus` receiver (#1511) - Convert Zipkin receiver and exporter to use OTLP (#1446) ### 🧰 Bug fixes 🧰 - Infer OpenCensus resource type based on OpenTelemetry's semantic conventions (#1462) - Fix log adapter in `prometheus` receiver (#1493) - Avoid frequent errors for process telemetry on Windows (#1487) ## v0.7.0 Beta ### 🚀 New components 🚀 - Receivers - `fluentforward` runs a TCP server that accepts events via the [Fluent Forward protocol](https://github.com/fluent/fluentd/wiki/Forward-Protocol-Specification-v1) (#1173) - Exporters - `kafka` exports traces to Kafka (#1439) - Extensions - **Experimental** `fluentbit` facilitates running a FluentBit subprocess of the collector (#1381) ### 💡 Enhancements 💡 - Updated `golang/protobuf` from v1.3.5 to v1.4.2 (#1308) - Updated `opencensus-proto` from v0.2.1 to v0.3.0 (#1308) - Added round_robin `balancer_name` as an option to gRPC client settings (#1353) - `hostmetrics` receiver - Switch to using perf counters to get disk io metrics on Windows (#1340) - Add device filter for file system (#1379) and disk (#1378) scrapers - Record process physical & virtual memory stats separately (#1403) - Scrape system.disk.time on Windows (#1408) - Add disk.pending_operations metric (#1428) - Add network interface label to network metrics (#1377) - Add `exporterhelper` (#1351) and `processorhelper` (#1359) factories - Update OTLP to latest version (#1384) - Disable timeout, retry on failure and sending queue for `logging` exporter (#1400) - Add support for retry and sending queue for `jaeger` exporter (#1401) - Add batch size bytes metric to `batch` processor (#1270) - `otlp` receiver: Add Log Support (#1444) - Allow to configure read/write buffer sizes for http Client (#1447) - Update DB conventions to latest and add exception conventions (#1452) ### 🧰 Bug fixes 🧰 - Fix `resource` processor for old metrics (#1412) - `jaeger` receiver: Do not try to stop if failed to start. Collector service will do that (#1434) ## v0.6.0 Beta ### 🛑 Breaking changes 🛑 - Renamed the metrics generated by `hostmetrics` receiver to match the (currently still pending) OpenTelemetry system metric conventions (#1261) (#1269) - Removed `vmmetrics` receiver (#1282) - Removed `cpu` scraper `report_per_cpu` config option (#1326) ### 💡 Enhancements 💡 - Added disk merged (#1267) and process count (#1268) metrics to `hostmetrics` - Log metric data points in `logging` exporter (#1258) - Changed the `batch` processor to not ignore the errors returned by the exporters (#1259) - Build and publish MSI (#1153) and DEB/RPM packages (#1278, #1335) - Added batch size metric to `batch` processor (#1241) - Added log support for `memorylimiter` processor (#1291) and `logging` exporter (#1298) - Always add tags for `observability`, other metrics may use them (#1312) - Added metrics support (#1313) and allow partial retries in `queued_retry` processor (#1297) - Update `resource` processor: introduce `attributes` config parameter to specify actions on attributes similar to `attributes` processor, old config interface is deprecated (#1315) - Update memory state labels for non-Linux OSs (#1325) - Ensure tcp connection value is provided for all states, even when count is 0 (#1329) - Set `batch` processor channel size to num cpus (#1330) - Add `send_batch_max_size` config parameter to `batch` processor enforcing hard limit on batch size (#1310) - Add support for including a per-RPC authentication to gRPC settings (#1250) ### 🧰 Bug fixes 🧰 - Fixed OTLP waitForReady, not set from config (#1254) - Fixed all translation diffs between OTLP and Jaeger (#1222) - Disabled `process` scraper for any non Linux/Windows OS (#1328) ## v0.5.0 Beta ### 🛑 Breaking changes 🛑 - **Update OTLP to v0.4.0 (#1142)**: Collector will be incompatible with any other sender or receiver of OTLP protocol of different versions - Make "--new-metrics" command line flag the default (#1148) - Change `endpoint` to `url` in Zipkin exporter config (#1186) - Change `tls_credentials` to `tls_settings` in Jaeger receiver config (#1233) - OTLP receiver config change for `protocols` to support mTLS (#1223) - Remove `export_resource_labels` flag from Zipkin exporter (#1163) ### 🚀 New components 🚀 - Receivers - Added process scraper to the `hostmetrics` receiver (#1047) ### 💡 Enhancements 💡 - otlpexporter: send configured headers in request (#1130) - Enable Collector to be run as a Windows service (#1120) - Add config for HttpServer (#1196) - Allow cors in HTTPServerSettings (#1211) - Add a generic grpc server settings config, cleanup client config (#1183) - Rely on gRPC to batch and loadbalance between connections instead of custom logic (#1212) - Allow to tune the read/write buffers for gRPC clients (#1213) - Allow to tune the read/write buffers for gRPC server (#1218) ### 🧰 Bug fixes 🧰 - Handle overlapping metrics from different jobs in prometheus exporter (#1096) - Fix handling of SpanKind INTERNAL in OTLP OC translation (#1143) - Unify zipkin v1 and v2 annotation/tag parsing logic (#1002) - mTLS: Add support to configure client CA and enforce ClientAuth (#1185) - Fixed untyped Prometheus receiver bug (#1194) - Do not embed ProtocolServerSettings in gRPC (#1210) - Add Context to the missing CreateMetricsReceiver method (#1216) ## v0.4.0 Beta Released 2020-06-16 ### 🛑 Breaking changes 🛑 - `isEnabled` configuration option removed (#909) - `thrift_tchannel` protocol moved from `jaeger` receiver to `jaeger_legacy` in contrib (#636) ### ⚠️ Major changes ⚠️ - Switch from `localhost` to `0.0.0.0` by default for all receivers (#1006) - Internal API Changes (only impacts contributors) - Add context to `Start` and `Stop` methods in the component (#790) - Rename `AttributeValue` and `AttributeMap` method names (#781) (other breaking changes in the internal trace data types) - Change entire repo to use the new vanityurl go.opentelemetry.io/collector (#977) ### 🚀 New components 🚀 - Receivers - `hostmetrics` receiver with CPU (#862), disk (#921), load (#974), filesystem (#926), memory (#911), network (#930), and virtual memory (#989) support - Processors - `batch` for batching received metrics (#1060) - `filter` for filtering (dropping) received metrics (#1001) ### 💡 Enhancements 💡 - `otlp` receiver implement HTTP X-Protobuf (#1021) - Exporters: Support mTLS in gRPC exporters (#927) - Extensions: Add `zpages` for service (servicez, pipelinez, extensions) (#894) ### 🧰 Bug fixes 🧰 - Add missing logging for metrics at `debug` level (#1108) - Fix setting internal status code in `jaeger` receivers (#1105) - `zipkin` export fails on span without timestamp when used with `queued_retry` (#1068) - Fix `zipkin` receiver status code conversion (#996) - Remove extra send/receive annotations with using `zipkin` v1 (#960) - Fix resource attribute mutation bug when exporting in `jaeger` proto (#907) - Fix metric/spans count, add tests for nil entries in the slices (#787) ### 🧩 Components 🧩 #### Traces | Receivers | Processors | Exporters | |:----------:|:--------------:|:----------:| | Jaeger | Attributes | File | | OpenCensus | Batch | Jaeger | | OTLP | Memory Limiter | Logging | | Zipkin | Queued Retry | OpenCensus | | | Resource | OTLP | | | Sampling | Zipkin | | | Span | | #### Metrics | Receivers | Processors | Exporters | |:-----------:|:--------------:|:----------:| | HostMetrics | Batch | File | | OpenCensus | Filter | Logging | | OTLP | Memory Limiter | OpenCensus | | Prometheus | | OTLP | | VM Metrics | | Prometheus | #### Extensions - Health Check - Performance Profiler - zPages ## v0.3.0 Beta Released 2020-03-30 ### Breaking changes - Make prometheus receiver config loading strict. #697 Prometheus receiver will now fail fast if the config contains unused keys in it. ### Changes and fixes - Enable best effort serve by default of Prometheus Exporter (https://github.com/orijtech/prometheus-go-metrics-exporter/pull/6) - Fix null pointer exception in the logging exporter #743 - Remove unnecessary condition to have at least one processor #744 ### Components | Receivers / Exporters | Processors | Extensions | |:---------------------:|:--------------:|:--------------------:| | Jaeger | Attributes | Health Check | | OpenCensus | Batch | Performance Profiler | | OpenTelemetry | Memory Limiter | zPages | | Zipkin | Queued Retry | | | | Resource | | | | Sampling | | | | Span | | ## v0.2.8 Alpha Alpha v0.2.8 of OpenTelemetry Collector - Implemented OTLP receiver and exporter. - Added ability to pass config to the service programmatically (useful for custom builds). - Improved own metrics / observability. - Refactored component and factory interface definitions (breaking change #683) ## v0.2.7 Alpha Alpha v0.2.7 of OpenTelemetry Collector - Improved error handling on shutdown - Partial implementation of new metrics (new obsreport package) - Include resource labels for Zipkin exporter - New `HASH` action to attribute processor ## v0.2.6 Alpha Alpha v0.2.6 of OpenTelemetry Collector. - Update metrics prefix to `otelcol` and expose command line argument to modify the prefix value. - Extend Span processor to have include/exclude span logic. - Batch dropped span now emits zero when no spans are dropped. ## v0.2.5 Alpha Alpha v0.2.5 of OpenTelemetry Collector. - Regexp-based filtering of spans based on service names. - Ability to choose strict or regexp matching for include/exclude filters. ## v0.2.4 Alpha Alpha v0.2.4 of OpenTelemetry Collector. - Regexp-based filtering of span names. - Ability to extract attributes from span names and rename span. - File exporter for debugging. - Span processor is now enabled by default. ## v0.2.3 Alpha Alpha v0.2.3 of OpenTelemetry Collector. Changes: 21a70d6 Add a memory limiter processor (#498) 9778b16 Refactor Jaeger Receiver config (#490) ec4ad0c Remove workers from OpenCensus receiver implementation (#497) 4e01fa3 Update k8s config to use opentelemetry docker image and configuration (#459) ## v0.2.2 Alpha Alpha v0.2.2 of OpenTelemetry Collector. Main changes visible to users since previous release: - Improved Testbed and added more E2E tests. - Made component interfaces more uniform (this is a breaking change). Note: v0.2.1 never existed and is skipped since it was tainted in some dependencies. ## v0.2.0 Alpha Alpha v0.2 of OpenTelemetry Collector. Docker image: omnition/opentelemetry-collector:v0.2.0 (we are working on getting this under an OpenTelemetry org) Main changes visible to users since previous release: - Rename from `service` to `collector`, the binary is now named `otelcol` - Configuration reorganized and using strict mode - Concurrency issues for pipelines transforming data addressed Commits: ```terminal 0e505d5 Refactor config: pipelines now under service (#376) 402b80c Add Capabilities to Processor and use for Fanout cloning decision (#374) b27d824 Use strict mode to read config (#375) d769eb5 Fix concurrency handling when data is fanned out (#367) dc6b290 Rename all github paths from opentelemetry-service to opentelemetry-collector (#371) d038801 Rename otelsvc to otelcol (#365) c264e0e Add Include/Exclude logic for Attributes Processor (#363) 8ce427a Pin a commit for Prometheus dependency in go.mod (#364) 2393774 Bump Jaeger version to 1.14.0 (latest) (#349) 63362d5 Update testbed modules (#360) c0e2a27 Change dashes to underscores to separate words in config files (#357) 7609eaa Rename OpenTelemetry Service to Collector in docs and comments (#354) bc5b299 Add common gRPC configuration settings (#340) b38505c Remove network access popups on macos (#348) f7727d1 Fixed loop variable pointer bug in jaeger translator (#341) 958beed Ensure that ConsumeMetricsData() is not passed empty metrics in the Prometheus receiver (#345) 0be295f Change log statement in Prometheus receiver from info to debug. (#344) d205393 Add Owais to codeowners (#339) 8fa6afe Translate OC resource labels to Jaeger process tags (#325) ``` ## v0.0.2 Alpha Alpha release of OpenTelemetry Service. Docker image: omnition/opentelemetry-service:v0.0.2 (we are working on getting this under an OpenTelemetry org) Main changes visible to users since previous release: ```terminal 8fa6afe Translate OC resource labels to Jaeger process tags (#325) 047b0f3 Allow environment variables in config (#334) 96c24a3 Add exclude/include spans option to attributes processor (#311) 4db0414 Allow metric processors to be specified in pipelines (#332) c277569 Add observability instrumentation for Prometheus receiver (#327) f47aa79 Add common configuration for receiver tls (#288) a493765 Refactor extensions to new config format (#310) 41a7afa Add Span Processor logic 97a71b3 Use full name for the metrics and spans created for observability (#316) fed4ed2 Add support to record metrics for metricsexporter (#315) 5edca32 Add include_filter configuration to prometheus receiver (#298) 0068d0a Passthrough CORS allowed origins (#260) ``` ## v0.0.1 Alpha This is the first alpha release of OpenTelemetry Service. Docker image: omnition/opentelemetry-service:v0.0.1 [v0.3.0]: https://github.com/open-telemetry/opentelemetry-collector/compare/v0.2.10...v0.3.0 [v0.2.10]: https://github.com/open-telemetry/opentelemetry-collector/compare/v0.2.8...v0.2.10 [v0.2.8]: https://github.com/open-telemetry/opentelemetry-collector/compare/v0.2.7...v0.2.8 [v0.2.7]: https://github.com/open-telemetry/opentelemetry-collector/compare/v0.2.6...v0.2.7 [v0.2.6]: https://github.com/open-telemetry/opentelemetry-collector/compare/v0.2.5...v0.2.6 [v0.2.5]: https://github.com/open-telemetry/opentelemetry-collector/compare/v0.2.4...v0.2.5 [v0.2.4]: https://github.com/open-telemetry/opentelemetry-collector/compare/v0.2.3...v0.2.4 [v0.2.3]: https://github.com/open-telemetry/opentelemetry-collector/compare/v0.2.2...v0.2.3 [v0.2.2]: https://github.com/open-telemetry/opentelemetry-collector/compare/v0.2.0...v0.2.2 [v0.2.0]: https://github.com/open-telemetry/opentelemetry-collector/compare/v0.0.2...v0.2.0 [v0.0.2]: https://github.com/open-telemetry/opentelemetry-collector/compare/v0.0.1...v0.0.2 [v0.0.1]: https://github.com/open-telemetry/opentelemetry-collector/tree/v0.0.1 ================================================ FILE: CLAUDE.md ================================================ @AGENTS.md ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing Guide We'd love your help! Please join our weekly [SIG meeting](https://github.com/open-telemetry/community#special-interest-groups). ## Target audiences The OpenTelemetry Collector has three main target audiences: 1. *End-users*, aiming to use an OpenTelemetry Collector binary. 1. *Component developers*, consuming the Go APIs to create components compatible with the OpenTelemetry Collector Builder. 1. *Collector library users*, consuming other Go APIs exposed by the opentelemetry-collector repository, for example to build custom distributions or other projects building on top of the Collector Go APIs. When the needs of these audiences conflict, end-users should be prioritized, followed by component developers, and finally Collector library users. ### End-users End-users are the target audience for our binary distributions, as made available via the [opentelemetry-collector-releases](https://github.com/open-telemetry/opentelemetry-collector-releases) repository, as well as distributions created using the [OpenTelemetry Collector Builder](https://github.com/open-telemetry/opentelemetry-collector/tree/main/cmd/builder). To them, stability in the behavior is important, be it runtime, configuration, or [internal telemetry](https://opentelemetry.io/docs/collector/internal-telemetry/). They are more numerous and harder to get in touch with, making our changes to the Collector more disruptive to them than to other audiences. As a general rule, whenever you are developing OpenTelemetry Collector components (extensions, receivers, processors, exporters, connectors), you should have end-users' interests in mind. Similarly, changes to code within packages like `config` will have an impact on this audience. Make sure to cause minimal disruption when doing changes here. ### Component developers Component developers create new extensions, receivers, processors, exporters, and connectors to be used with the OpenTelemetry Collector. They are the primary audience for the opentelemetry-collector repository's public Go API. A significant part of them will contribute to opentelemetry-collector-contrib. In addition to the end-user aspect mentioned above, this audience also cares about Go API compatibility of Go modules such as the ones in the `pdata`, `component`, `consumer`, `confmap`, `exporterhelper`, `config*` modules and others, even though such changes wouldn't cause any impact to end-users. See the [Breaking changes](docs/coding-guidelines.md#breaking-changes) section in the coding guidelines for more information on how to perform changes affecting this audience. ### Collector library users A third audience uses the OpenTelemetry Collector as a library to build their own distributions or other projects based on the Collector. This audience is the main consumer of modules such as `service` or `otelcol`. They also share the same concerns as component developers regarding Go API compatibility and are likewise interested in behavior stability. These are our most advanced users and are the most equipped to deal with disruptive changes. ## How to structure PRs to get expedient reviews? We recommend that PRs be smaller than 500 lines, excluding `go.mod` and `go.sum` changes, to help reviewers perform thorough and timely reviews. Keep each PR isolated to the specific change it is meant to deliver. Do not bundle unrelated cleanup, drive-by edits, opportunistic refactors, wording tweaks, formatting changes, or other "small" improvements into the same PR unless they are strictly necessary to make the intended change correct. Narrow, focused PRs are significantly easier to review and validate. ### When adding a new component Components refer to connectors, exporters, extensions, processors, and receivers. The key criteria for implementing a component is to: * Implement the `component.Component` interface * Provide a configuration structure which defines the configuration of the component * Provide the implementation that performs the component operation For more details on components, see the [Donating New Components](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#donating-new-components) document and the tutorial [Building a Trace Receiver](https://opentelemetry.io/docs/collector/trace-receiver/) which provides a detailed example of building a component. When adding a new component to the OpenTelemetry Collector, ensure that any configuration structs used by the component include fields with the `configopaque.String` type for sensitive data. This ensures that the data is masked when serialized to prevent accidental exposure. When submitting a component to the community, consider breaking it down into separate PRs as follows: * **First PR** should include the overall structure of the new component: * Readme, configuration, and factory implementation should usually use the helper factory structs. * This PR is usually trivial to review, so the size limit does not apply to it. * The component should use [`In Development` Stability](https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#development) in its README. * **Second PR** should include the concrete implementation of the component. If the size of this PR is larger than the recommended size consider splitting it into multiple PRs. * **Last PR** should mark the new component as `Alpha` stability and add it to the `otelcorecol` binary by updating the `otelcorecol/components.go` file. The component must be enabled only after sufficient testing and only when it meets [`Alpha` stability requirements.](https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#alpha) * Once a new component has been added to the executable, please add the component to the [OpenTelemetry.io registry](https://github.com/open-telemetry/opentelemetry.io#adding-a-project-to-the-opentelemetry-registry). * intra-repository `replace` statements in `go.mod` files can be automatically inserted by running `make crosslink`. For more information on the `crosslink` tool see the README [here](https://github.com/open-telemetry/opentelemetry-go-build-tools/tree/main/crosslink). ### Refactoring Work Any refactoring work must be split in its own PR that does not include any behavior changes. It is important to do this to avoid hidden changes in large and trivial refactoring PRs. If you notice additional improvement opportunities while working on a change, handle them in follow-up PRs instead of extending the current one beyond its original scope. ## Report a bug or request a feature Reporting bugs is an important contribution. Please make sure to include: * Expected and actual behavior * The OpenTelemetry version you are running * If possible, steps to reproduce ### Adding Labels via Comments In order to facilitate proper label usage and to empower Code Owners, you are able to add labels to issues via comments. To add a label through a comment, post a new comment on an issue starting with `/label`, followed by a space-separated list of your desired labels. Supported labels come from the table below, or correspond to a component defined in the [CODEOWNERS file](.github/CODEOWNERS). The following general labels are supported: | Label | Label in Comment | |--------------------------|--------------------------| | `arm64` | `arm64` | | `good first issue` | `good-first-issue` | | `help wanted` | `help-wanted` | | `discussion needed` | `discussion-needed` | | `os:macos` | `os:macos` | | `os:windows` | `os:windows` | | `waiting for author` | `waiting-for-author` | | `waiting-for-codeowners` | `waiting-for-codeowners` | | `bug` | `bug` | | `priority:p0` | `priority:p0` | | `priority:p1` | `priority:p1` | | `priority:p2` | `priority:p2` | | `priority:p3` | `priority:p3` | | `Stale` | `stale` | To delete a label, prepend the label with `-`. Note that you must make a new comment to modify labels; you cannot edit an existing comment. Example label comment: ``` /label help-wanted -arm64 ``` ### Rerunning Failed Workflows PR authors can rerun failed GitHub Actions workflows by commenting `/rerun` on the pull request. This will automatically rerun all failed workflow runs for the PR's latest commit. Example rerun comment: ``` /rerun ``` ## How to contribute ### Before you start Please read the project contribution [guide](https://github.com/open-telemetry/community/tree/main/guides/contributor) for general practices for the OpenTelemetry project. Select a good issue from the links below (ordered by difficulty/complexity): * [Good First Issue](https://github.com/open-telemetry/opentelemetry-collector/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) * [Help Wanted](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) Comment on the issue that you want to work on so we can assign it to you and clarify anything related to it. If you would like to work on something that is not listed as an issue (e.g. a new feature or enhancement) please first read our [vision](docs/vision.md) to make sure your proposal aligns with the goals of the Collector, then create an issue and describe your proposal. It is best to do this in advance so that maintainers can decide if the proposal is a good fit for this repository. This will help avoid situations when you spend significant time on something that maintainers may decide this repo is not the right place for. If you're new to the Collector, the [internal architecture](docs/internal-architecture.md) documentation may be helpful. Follow the instructions below to create your PR. ### Fork In the interest of keeping this repository clean and manageable, you should work from a fork. To create a fork, click the 'Fork' button at the top of the repository, then clone the fork locally using `git clone git@github.com:USERNAME/opentelemetry-collector.git`. You should also add this repository as an "upstream" repo to your local copy, in order to keep it up to date. You can add this as a remote like so: `git remote add upstream https://github.com/open-telemetry/opentelemetry-collector.git` Verify that the upstream exists: `git remote -v` To update your fork, fetch the upstream repo's branches and commits, then merge your `main` with upstream's `main`: ``` git fetch upstream git checkout main git merge upstream/main ``` Remember to always work in a branch of your local copy, as you might otherwise have to contend with conflicts in `main`. Please also see [GitHub workflow](https://github.com/open-telemetry/community/blob/main/guides/contributor/processes.md#github-workflow) section of the general project contributing guide. ## Required Tools Working with the project sources requires the following tools: 1. [git](https://git-scm.com/) 2. [go](https://golang.org/) (version 1.25 and up) 3. [make](https://www.gnu.org/software/make/) 4. [docker](https://www.docker.com/) ## Repository Setup Fork the repo and checkout the upstream repo to your GOPATH by: ``` $ git clone git@github.com:open-telemetry/opentelemetry-collector.git ``` Add your fork as an origin: ```shell $ cd opentelemetry-collector $ git remote add fork git@github.com:YOUR_GITHUB_USERNAME/opentelemetry-collector.git ``` Run tests, fmt, and lint: ```shell $ make ``` You can run `make markdownlint` to check Markdown formatting. ## Creating a PR Checkout a new branch, make modifications, build locally, and push the branch to your fork to open a new PR: ```shell $ git checkout -b feature # edit $ make $ make fmt $ git commit $ git push fork feature ``` ### Commit Messages Use descriptive commit messages. Here are [some recommendations](https://cbea.ms/git-commit/) on how to write good commit messages. When creating PRs GitHub will automatically copy commit messages into the PR description, so it is a useful habit to write good commit messages before the PR is created. Also, unless you actually want to tell a story with multiple commits make sure to squash into a single commit before creating the PR. When maintainers merge PRs with multiple commits, they will be squashed and GitHub will concatenate all commit messages right before you hit the "Confirm squash and merge" button. Maintainers must make sure to edit this concatenated message to make it right before merging. In some cases, if the commit messages are lacking the easiest approach to have at least something useful is copy/pasting the PR description into the commit message box before merging (but see the above paragraph about writing good commit messages in the first place). ## General Notes This project uses Go 1.25.* and [Github Actions.](https://github.com/features/actions) It is recommended to run `make gofmt all` before submitting your PR. ## Coding Guidelines See the [Coding Guidelines](docs/coding-guidelines.md) document for more information. ## Changelog ### Overview There are two Changelogs for this repository: - `CHANGELOG.md` is intended for users of the collector and lists changes that affect the behavior of the collector. - `CHANGELOG-API.md` is intended for developers who are importing packages from the collector codebase. ### When to add a Changelog Entry An entry into the changelog is required for the following reasons: - Changes made to the behaviour of the component - Changes to the configuration - Changes to default settings - New components being added - Changes to exported elements of a package It is reasonable to omit an entry to the changelog under these circumstances: - Updating test to remove flakiness or improve coverage - Updates to the CI/CD process - Updates to internal packages If there is some uncertainty with regards to if a changelog entry is needed, the recommendation is to create an entry to in the event that the change is important to the project consumers. ### Adding a Changelog Entry The [CHANGELOG.md](./CHANGELOG.md) and [CHANGELOG-API.md](./CHANGELOG-API.md) files in this repo is autogenerated from `.yaml` files in the `./.chloggen` directory. Your pull request should add a new `.yaml` file to this directory. The name of your file must be unique since the last release. During the collector release process, all `./chloggen/*.yaml` files are transcribed into `CHANGELOG.md` and `CHANGELOG-API.md` and then deleted. **Recommended Steps** 1. Create an entry file using `make chlog-new`. This generates a file based on your current branch (e.g. `./.chloggen/my-branch.yaml`) 2. Fill in all fields in the new file 3. Run `make chlog-validate` to ensure the new file is valid 4. Commit and push the file Alternatively, copy `./.chloggen/TEMPLATE.yaml`, or just create your file from scratch. ## Local Testing To manually test your changes, follow these steps to build and run the Collector locally. Ensure that you execute these commands from the root of the repository: 1. Build the Collector: ```shell make otelcorecol ``` 2. Run the Collector with a local configuration file: ```shell ./bin/otelcorecol__ --config ./examples/local/otel-config.yaml ``` The actual name of the binary will depend on your platform, adjust accordingly (e.g., `./bin/otelcorecol_darwin_arm64`). Replace `otel-config.yaml` with the appropriate configuration file as needed. 3. Verify that your changes are reflected in the Collector's behavior by testing it against the provided configuration. ## Membership, Roles, and Responsibilities ### Membership levels See the [OpenTelemetry membership guide](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md) for information on how to become a member of the OpenTelemetry organization and the different roles available. In addition to the roles listed there we also have a Collector-specific role: code owners. ### Becoming a Code Owner A Code Owner is responsible for one or multiple packages within the Collector. That responsibility includes maintaining the component, triaging and responding to issues, and reviewing pull requests. Maintainers are expected to seek feedback from code owners for changes that are not trivial, but they may merge PRs without code owner approval. Code Ownership does not have to be a full-time job. If you can find a couple hours to help out on a recurring basis, please consider pursuing Code Ownership. #### Requirements If you would like to help and become a Code Owner, you must meet the following requirements. These are more stringent requirements than those in the opentelemetry-collector-contrib repository due to the higher impact of changes in this repository: 1. [Be a member of the OpenTelemetry organization](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#member). 2. Be an existing [approver](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#approver) or [maintainer](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#maintainer) in at least one repository within the OpenTelemetry Github organization. 3. Have made significant contributions directly to the package you want to own. #### How to become a Code Owner To become a Code Owner, open a PR that adds you to the [.github/CODEOWNERS](.github/CODEOWNERS) file. Make sure to ping existing code owners for the package you want to own to get their approval. ## Release See [release](docs/release.md) for details. ## Contributing Images If you are adding any new images, please use [Excalidraw](https://excalidraw.com). It's a free and open-source web application and doesn't require any account to get started. Once you've created the design, while exporting the image, make sure to tick **"Embed scene into exported file"** option. This allows the image to be imported in an editable format for other contributors later. ## Common Issues Build fails due to dependency issues, e.g. ```sh go: github.com/golangci/golangci-lint@v1.31.0 requires github.com/tommy-muehle/go-mnd@v1.3.1-0.20200224220436-e6f9a994e8fa: invalid pseudo-version: git fetch --unshallow -f origin in /root/go/pkg/mod/cache/vcs/053b1e985f53e43f78db2b3feaeb7e40a2ae482c92734ba3982ca463d5bf19ce: exit status 128: fatal: git fetch-pack: expected shallow list ``` `go env GOPROXY` should return `https://proxy.golang.org,direct`. If it does not, set it as an environment variable: `export GOPROXY=https://proxy.golang.org,direct` ### Makefile Guidelines When adding or modifying the `Makefile`'s in this repository, consider the following design guidelines. Make targets are organized according to whether they apply to the entire repository, or only to an individual module. The [Makefile](./Makefile) SHOULD contain "repo-level" targets. (i.e. targets that apply to the entire repo.) Likewise, `Makefile.Common` SHOULD contain "module-level" targets. (i.e. targets that apply to one module at a time.) Each module should have a `Makefile` at its root that includes `Makefile.Common`. #### Module-level targets Module-level targets SHOULD NOT act on nested modules. For example, running `make lint` at the root of the repo will _only_ evaluate code that is part of the `go.opentelemetry.io/collector` module. This excludes nested modules such as `go.opentelemetry.io/collector/component`. Each module-level target SHOULD have a corresponding repo-level target. For example, `make golint` will run `make lint` in each module. In this way, the entire repository is covered. The root `Makefile` contains some "for each module" targets that can wrap a module-level target into a repo-level target. #### Repo-level targets Whenever reasonable, targets SHOULD be implemented as module-level targets (and wrapped with a repo-level target). However, there are many valid justifications for implementing a standalone repo-level target. 1. The target naturally applies to the repo as a whole. (e.g. Building the collector.) 2. Interaction between modules would be problematic. 3. A necessary tool does not provide a mechanism for scoping its application. (e.g. `porto` cannot be limited to a specific module.) 4. The "for each module" pattern would result in incomplete coverage of the codebase. (e.g. A target that scans all files, not just `.go` files.) #### Default targets The default module-level target (i.e. running `make` in the context of an individual module), should run a substantial set of module-level targets for an individual module. Ideally, this would include *all* module-level targets, but exceptions should be made if a particular target would result in unacceptable latency in the local development loop. The default repo-level target (i.e. running `make` at the root of the repo) should meaningfully validate the entire repo. This should include running the default common target for each module as well as additional repo-level targets. ## How to update the OTLP protocol version When a new OTLP version is published, the following steps are required to update this code base: 1. Edit the top-level Makefile's `OPENTELEMETRY_PROTO_VERSION` variable 2. Run `make genproto` 3. Inspect modifications to the generated code in `pdata/internal/data/protogen` 4. When new fields are added in the protocol, make corresponding changes in `pdata/internal/cmd/pdatagen/internal` 5. Run `make genpdata` 6. Inspect modifications to the generated code in `pdata/*` 7. Run `make genproto-cleanup`, to remove temporary files 8. Update the supported OTLP version in [README.md](./README.md). ## Exceptions While the rules in this and other documents in this repository are what we strive to follow, we acknowledge that it may be unfeasible to apply these rules in some situations. Exceptions to the rules on this and other documents are acceptable if consensus can be obtained from approvers in the pull request they are proposed. A reason for requesting the exception MUST be given in the pull request. Until unanimity is obtained, approvers and maintainers are encouraged to discuss the issue at hand. If a consensus (unanimity) cannot be obtained, the maintainers' group is then tasked with making a decision using its regular means (voting, TC help, etc.). ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: Makefile ================================================ include ./Makefile.Common # This is the code that we want to run lint, etc. ALL_SRC := $(shell find . -name '*.go' \ -not -path './internal/tools/*' \ -not -path '*/third_party/*' \ -not -path './pdata/internal/data/protogen/*' \ -not -path './service/internal/zpages/tmplgen/*' \ -type f | sort) # All source code and documents. Used in spell check. ALL_DOC := $(shell find . \( -name "*.md" -o -name "*.yaml" \) \ -type f | sort) # All Markdown files. Used in markdownlint. ALL_MD := $(shell find . -name "*.md" -type f | sort) # ALL_MODULES includes ./* dirs (excludes . dir) ALL_MODULES := $(shell find . -mindepth 2 \ -type f \ -name "go.mod" \ -not -path "./internal/tools/*" \ -exec dirname {} \; | sort ) CMD?= RUN_CONFIG?=examples/local/otel-config.yaml CONTRIB_PATH=$(CURDIR)/../opentelemetry-collector-contrib COMP_REL_PATH=cmd/otelcorecol/components.go MOD_NAME=go.opentelemetry.io/collector # Function to execute a command. Note the empty line before endef to make sure each command # gets executed separately instead of concatenated with previous one. # Accepts command to execute as first parameter. define exec-command $(1) endef .DEFAULT_GOAL := all .PHONY: all all: checklicense checkdoc misspell markdownlint goimpi goporto multimod-verify golint gotest all-modules: @echo $(ALL_MODULES) | tr ' ' '\n' | sort .PHONY: gomoddownload gomoddownload: @$(MAKE) for-all-target TARGET="moddownload" .PHONY: gotest gotest: @$(MAKE) for-all-target TARGET="test" .PHONY: gobenchmark gobenchmark: @$(MAKE) for-all-target TARGET="benchmark" cat `find . -name benchmark.txt` > benchmarks.txt .PHONY: gotest-with-cover gotest-with-cover: @$(MAKE) for-all-target TARGET="test-with-cover" $(GOCMD) tool covdata textfmt -i=./coverage/unit -o ./coverage.txt .PHONY: gotest-with-junit gotest-with-junit: @$(MAKE) for-all-target TARGET="test-with-junit" .PHONY: goporto goporto: $(GO_TOOL) porto -w --include-internal --skip-dirs "^cmd/mdatagen/third_party$$" ./ .PHONY: for-all for-all: @echo "running $${CMD} in root" @$${CMD} @set -e; for dir in $(GOMODULES); do \ (cd "$${dir}" && \ echo "running $${CMD} in $${dir}" && \ $${CMD} ); \ done .PHONY: golint golint: @$(MAKE) for-all-target TARGET="lint" .PHONY: gomodernize gomodernize: @$(MAKE) for-all-target TARGET="modernize" .PHONY: goimpi goimpi: @$(MAKE) for-all-target TARGET="impi" .PHONY: gofmt gofmt: @$(MAKE) for-all-target TARGET="fmt" .PHONY: gotidy gotidy: @$(MAKE) for-all-target TARGET="tidy" .PHONY: gogenerate gogenerate: cd cmd/mdatagen && $(GOCMD) install . @$(MAKE) for-all-target TARGET="generate" $(MAKE) fmt .PHONY: govulncheck govulncheck: @$(MAKE) for-all-target TARGET="vulncheck" .PHONY: addlicense addlicense: @ADDLICENSEOUT=`$(GO_TOOL) addlicense -s=only -y "" -c "The OpenTelemetry Authors" $(ALL_SRC) 2>&1`; \ if [ "$$ADDLICENSEOUT" ]; then \ echo "$(ADDLICENSE) FAILED => add License errors:\n"; \ echo "$$ADDLICENSEOUT\n"; \ exit 1; \ else \ echo "Add License finished successfully"; \ fi .PHONY: checklicense checklicense: @licRes=$$(for f in $$(find . -type f \( -iname '*.go' -o -iname '*.sh' \) ! -path '**/third_party/*') ; do \ awk '/Copyright The OpenTelemetry Authors|generated|GENERATED/ && NR<=3 { found=1; next } END { if (!found) print FILENAME }' $$f; \ awk '/SPDX-License-Identifier: Apache-2.0|generated|GENERATED/ && NR<=4 { found=1; next } END { if (!found) print FILENAME }' $$f; \ done); \ if [ -n "$${licRes}" ]; then \ echo "license header checking failed:"; echo "$${licRes}"; \ exit 1; \ fi .PHONY: misspell misspell: $(GO_TOOL) misspell -error $(ALL_DOC) .PHONY: markdownlint markdownlint: npx -y markdownlint-cli@0.48.0 -c .markdownlint.yaml --ignore-path .markdownlintignore -- $(ALL_MD) .PHONY: misspell-correction misspell-correction: $(GO_TOOL) misspell -w $(ALL_DOC) .PHONY: run run: otelcorecol ./bin/otelcorecol_$(GOOS)_$(GOARCH) --config ${RUN_CONFIG} ${RUN_ARGS} # Append root module to all modules GOMODULES = $(ALL_MODULES) $(PWD) # Define a delegation target for each module .PHONY: $(GOMODULES) $(GOMODULES): @echo "Running target '$(TARGET)' in module '$@'" $(MAKE) -C $@ $(TARGET) # Triggers each module's delegation target .PHONY: for-all-target for-all-target: $(GOMODULES) .PHONY: check-component check-component: ifndef COMPONENT $(error COMPONENT variable was not defined) endif # Build the Collector executable. .PHONY: otelcorecol otelcorecol: pushd cmd/otelcorecol && CGO_ENABLED=0 $(GOCMD) build -trimpath -o ../../bin/otelcorecol_$(GOOS)_$(GOARCH) -tags "grpcnotrace" ./... && popd .PHONY: genotelcorecol genotelcorecol: pushd cmd/builder/ && $(GOCMD) run ./ --skip-compilation --config ../otelcorecol/builder-config.yaml --output-path ../otelcorecol && popd $(MAKE) -C cmd/otelcorecol fmt .PHONY: actionlint actionlint: $(GO_TOOL) actionlint -config-file .github/actionlint.yaml -color .github/workflows/*.yml .github/workflows/*.yaml .PHONY: ocb ocb: $(MAKE) -C cmd/builder config $(MAKE) -C cmd/builder ocb # Generate structs, functions and tests for pdata package. genpdata: cd internal/cmd/pdatagen && $(GOCMD) run main.go -C $(SRC_ROOT) $(MAKE) -C pdata fmt cd pdata && $(GO_TOOL) betteralign --generated_files --apply ./... || true DOCKERCMD ?= docker DOCKER_PROTOBUF ?= otel/build-protobuf:0.23.0 PROTO_SRC_DIRS := exporter/exporterhelper/internal/queue PROTO_FILES := $(foreach dir,$(PROTO_SRC_DIRS),$(wildcard $(dir)/*.proto)) PROTOC := $(DOCKERCMD) run --rm -u ${shell id -u} -v${PWD}:${PWD} -w${PWD} ${DOCKER_PROTOBUF} --proto_path=${PWD} --go_out=plugins=grpc,paths=source_relative:. .PHONY: genproto genproto: @echo "Generating Go code for proto files" @echo "Found proto files: $(PROTO_FILES)" $(foreach file,$(PROTO_FILES),$(call exec-command,$(PROTOC) $(file))) $(MAKE) fmt ALL_MOD_PATHS := "" $(ALL_MODULES:.%=%) .PHONY: prepare-contrib prepare-contrib: @echo Setting contrib at $(CONTRIB_PATH) to use this core checkout @$(MAKE) -j2 -C $(CONTRIB_PATH) for-all CMD="$(GOCMD) mod edit \ $(addprefix -replace ,$(join $(ALL_MOD_PATHS:%=go.opentelemetry.io/collector%=),$(ALL_MOD_PATHS:%=$(CURDIR)%)))" @$(MAKE) -j2 -C $(CONTRIB_PATH) gotidy @$(MAKE) generate-contrib # Checks that the HEAD of the contrib repo checked out in CONTRIB_PATH compiles # against the current version of this repo. .PHONY: check-contrib check-contrib: @echo -e "\nRunning tests" @$(MAKE) -C $(CONTRIB_PATH) gotest @if [ -z "$(SKIP_RESTORE_CONTRIB)" ]; then \ $(MAKE) restore-contrib; \ fi .PHONY: generate-contrib generate-contrib: @echo -e "\nGenerating files in contrib" $(MAKE) -C $(CONTRIB_PATH) generate GROUP=all # Restores contrib to its original state after running check-contrib. .PHONY: restore-contrib restore-contrib: @echo -e "\nRestoring contrib at $(CONTRIB_PATH) to its original state" @$(MAKE) -C $(CONTRIB_PATH) for-all CMD="$(GOCMD) mod edit \ $(addprefix -dropreplace ,$(ALL_MOD_PATHS:%=go.opentelemetry.io/collector%))" @$(MAKE) -C $(CONTRIB_PATH) for-all CMD="$(GOCMD) mod tidy" # List of directories where certificates are stored for unit tests. CERT_DIRS := localhost|""|config/configgrpc/testdata \ localhost|""|config/confighttp/testdata \ example1|"-1"|config/configtls/testdata \ example2|"-2"|config/configtls/testdata cert-domain = $(firstword $(subst |, ,$1)) cert-suffix = $(word 2,$(subst |, ,$1)) cert-dir = $(word 3,$(subst |, ,$1)) # Generate certificates for unit tests relying on certificates. .PHONY: certs certs: $(foreach dir, $(CERT_DIRS), $(call exec-command, @internal/buildscripts/gen-certs.sh -o $(call cert-dir,$(dir)) -s $(call cert-suffix,$(dir)) -m $(call cert-domain,$(dir)))) # Generate certificates for unit tests relying on certificates without copying certs to specific test directories. .PHONY: certs-dryrun certs-dryrun: @internal/buildscripts/gen-certs.sh -d .PHONY: checkapi checkapi: $(GO_TOOL) checkapi -folder . -config .checkapi.yaml # Verify existence of READMEs for components specified as default components in the collector. .PHONY: checkdoc checkdoc: $(GO_TOOL) checkfile --project-path $(CURDIR) --component-rel-path $(COMP_REL_PATH) --module-name $(MOD_NAME) --file-name "README.md" # Construct new API state snapshots .PHONY: apidiff-build apidiff-build: @$(foreach pkg,$(ALL_PKGS),$(call exec-command,./internal/buildscripts/gen-apidiff.sh -p $(pkg))) # If we are running in CI, change input directory ifeq ($(CI), true) APICOMPARE_OPTS=$(COMPARE_OPTS) else APICOMPARE_OPTS=-d "./internal/data/apidiff" endif # Compare API state snapshots .PHONY: apidiff-compare apidiff-compare: @$(foreach pkg,$(ALL_PKGS),$(call exec-command,./internal/buildscripts/compare-apidiff.sh -p $(pkg))) .PHONY: multimod-verify multimod-verify: @echo "Validating versions.yaml" $(GO_TOOL) multimod verify MODSET?=stable .PHONY: multimod-prerelease multimod-prerelease: $(GO_TOOL) multimod prerelease -s=true -b=false -v ./versions.yaml -m ${MODSET} $(MAKE) gotidy COMMIT?=HEAD REMOTE?=git@github.com:open-telemetry/opentelemetry-collector.git .PHONY: push-tags push-tags: $(GO_TOOL) multimod verify set -e; for tag in `$(GO_TOOL) multimod tag -m ${MODSET} -c ${COMMIT} --print-tags | grep -v "Using" `; do \ echo "pushing tag $${tag}"; \ git push ${REMOTE} $${tag}; \ done; .PHONY: check-changes check-changes: $(GO_TOOL) multimod diff -p $(PREVIOUS_VERSION) -m $(MODSET) .PHONY: prepare-release prepare-release: ifndef MODSET @echo "MODSET not defined" @echo "usage: make prepare-release RELEASE_CANDIDATE= PREVIOUS_VERSION= MODSET=beta" exit 1 endif ifdef PREVIOUS_VERSION @echo "Previous version $(PREVIOUS_VERSION)" else @echo "PREVIOUS_VERSION not defined" @echo "usage: make prepare-release RELEASE_CANDIDATE= PREVIOUS_VERSION= MODSET=beta" exit 1 endif ifdef RELEASE_CANDIDATE @echo "Preparing ${MODSET} release $(RELEASE_CANDIDATE)" else @echo "RELEASE_CANDIDATE not defined" @echo "usage: make prepare-release RELEASE_CANDIDATE= PREVIOUS_VERSION= 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

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

Vision   •   Configuration   •   Monitoring   •   Security   •   Package

--- # OpenTelemetry Icon OpenTelemetry Collector The OpenTelemetry Collector offers a vendor-agnostic implementation on how to receive, process and export telemetry data. In addition, it removes the need to run, operate and maintain multiple agents/collectors in order to support open-source telemetry data formats (e.g. Jaeger, Prometheus, etc.) 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! Repo 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 | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aopen%20label%3Acmd%2Fmdatagen%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Acmd%2Fmdatagen) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aclosed%20label%3Acmd%2Fmdatagen%20&label=closed&color=blue&logo=opentelemetry)](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 | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aopen%20label%3Areceiver%2Fsamplefactory%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Areceiver%2Fsamplefactory) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aclosed%20label%3Areceiver%2Fsamplefactory%20&label=closed&color=blue&logo=opentelemetry)](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 | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aopen%20label%3Aprocessor%2Fsample%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Aprocessor%2Fsample) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aclosed%20label%3Aprocessor%2Fsample%20&label=closed&color=blue&logo=opentelemetry)](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 | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aopen%20label%3Areceiver%2Fsample%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Areceiver%2Fsample) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aclosed%20label%3Areceiver%2Fsample%20&label=closed&color=blue&logo=opentelemetry)](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 | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aopen%20label%3Ascraper%2Fsample%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Ascraper%2Fsample) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aclosed%20label%3Ascraper%2Fsample%20&label=closed&color=blue&logo=opentelemetry)](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 | [![Open issues](https://img.shields.io/github/issues-search/{{ .GithubProject }}?query=is%3Aissue%20is%3Aopen%20label%3A{{ $class }}%2F{{ $shortName }}%20&label=open&color=orange&logo=opentelemetry)](https://github.com/{{ .GithubProject }}/issues?q=is%3Aopen+is%3Aissue+label%3A{{ $class }}%2F{{ $shortName }}) [![Closed issues](https://img.shields.io/github/issues-search/{{ .GithubProject }}?query=is%3Aissue%20is%3Aclosed%20label%3A{{ $class }}%2F{{ $shortName }}%20&label=closed&color=blue&logo=opentelemetry)](https://github.com/{{ .GithubProject }}/issues?q=is%3Aclosed+is%3Aissue+label%3A{{ $class }}%2F{{ $shortName }}) | {{- if not .Status.DisableCodeCov }} | Code coverage | [![codecov](https://codecov.io/github/{{ .GithubProject }}/graph/main/badge.svg?component={{ .GetCodeCovComponentID }})](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 | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aopen%20label%3Acmd%2Ffoo%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aopen+is%3Aissue+label%3Acmd%2Ffoo) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aclosed%20label%3Acmd%2Ffoo%20&label=closed&color=blue&logo=opentelemetry)](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 | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aopen%20label%3Areceiver%2Ffoo%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Areceiver%2Ffoo) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aclosed%20label%3Areceiver%2Ffoo%20&label=closed&color=blue&logo=opentelemetry)](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 | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aopen%20label%3Areceiver%2Ffoo%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aopen+is%3Aissue+label%3Areceiver%2Ffoo) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aclosed%20label%3Areceiver%2Ffoo%20&label=closed&color=blue&logo=opentelemetry)](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 | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aopen%20label%3Areceiver%2Ffoo%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aopen+is%3Aissue+label%3Areceiver%2Ffoo) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aclosed%20label%3Areceiver%2Ffoo%20&label=closed&color=blue&logo=opentelemetry)](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 | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aopen%20label%3Areceiver%2Ffoo%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aopen+is%3Aissue+label%3Areceiver%2Ffoo) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aclosed%20label%3Areceiver%2Ffoo%20&label=closed&color=blue&logo=opentelemetry)](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 | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aopen%20label%3Aconverter%2Ffoo%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aopen+is%3Aissue+label%3Aconverter%2Ffoo) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aclosed%20label%3Aconverter%2Ffoo%20&label=closed&color=blue&logo=opentelemetry)](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 | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aopen%20label%3Aextension%2Ffoo%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aopen+is%3Aissue+label%3Aextension%2Ffoo) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aclosed%20label%3Aextension%2Ffoo%20&label=closed&color=blue&logo=opentelemetry)](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 | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aopen%20label%3Aprovider%2Ffoo%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aopen+is%3Aissue+label%3Aprovider%2Ffoo) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aclosed%20label%3Aprovider%2Ffoo%20&label=closed&color=blue&logo=opentelemetry)](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 | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aopen%20label%3Apkg%2Fconfmap%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Apkg%2Fconfmap) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aclosed%20label%3Apkg%2Fconfmap%20&label=closed&color=blue&logo=opentelemetry)](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 | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aopen%20label%3Aprovider%2Fenvprovider%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Aprovider%2Fenvprovider) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aclosed%20label%3Aprovider%2Fenvprovider%20&label=closed&color=blue&logo=opentelemetry)](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 | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aopen%20label%3Aprovider%2Ffileprovider%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Aprovider%2Ffileprovider) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aclosed%20label%3Aprovider%2Ffileprovider%20&label=closed&color=blue&logo=opentelemetry)](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 | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aopen%20label%3Aprovider%2Fhttpprovider%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Aprovider%2Fhttpprovider) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aclosed%20label%3Aprovider%2Fhttpprovider%20&label=closed&color=blue&logo=opentelemetry)](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 | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aopen%20label%3Aprovider%2Fhttpsprovider%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Aprovider%2Fhttpsprovider) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aclosed%20label%3Aprovider%2Fhttpsprovider%20&label=closed&color=blue&logo=opentelemetry)](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 | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aopen%20label%3Aprovider%2Fyamlprovider%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Aprovider%2Fyamlprovider) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aclosed%20label%3Aprovider%2Fyamlprovider%20&label=closed&color=blue&logo=opentelemetry)](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 | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aopen%20label%3Aconnector%2Fforward%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Aconnector%2Fforward) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aclosed%20label%3Aconnector%2Fforward%20&label=closed&color=blue&logo=opentelemetry)](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: ![State Diagram](img/component-status-state-diagram.png) 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. ![Status Event Generation](img/component-status-event-generation.png) ### 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** ![Runtime State Diagram](img/component-status-runtime-states.png) 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 | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aopen%20label%3Aexporter%2Fdebug%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Aexporter%2Fdebug) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aclosed%20label%3Aexporter%2Fdebug%20&label=closed&color=blue&logo=opentelemetry)](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 | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aopen%20label%3Aexporter%2Fnop%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Aexporter%2Fnop) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aclosed%20label%3Aexporter%2Fnop%20&label=closed&color=blue&logo=opentelemetry)](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 | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aopen%20label%3Aexporter%2Fotlp%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Aexporter%2Fotlp) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aclosed%20label%3Aexporter%2Fotlp%20&label=closed&color=blue&logo=opentelemetry)](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 | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aopen%20label%3Aexporter%2Fotlphttp%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Aexporter%2Fotlphttp) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aclosed%20label%3Aexporter%2Fotlphttp%20&label=closed&color=blue&logo=opentelemetry)](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 | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aopen%20label%3Aextension%2Fmemorylimiter%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Aextension%2Fmemorylimiter) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aclosed%20label%3Aextension%2Fmemorylimiter%20&label=closed&color=blue&logo=opentelemetry)](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 | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aopen%20label%3Aextension%2Fzpages%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Aextension%2Fzpages) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aclosed%20label%3Aextension%2Fzpages%20&label=closed&color=blue&logo=opentelemetry)](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 | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aopen%20label%3Apkg%2Fpdata%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Apkg%2Fpdata) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aclosed%20label%3Apkg%2Fpdata%20&label=closed&color=blue&logo=opentelemetry)](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 | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aopen%20label%3Aprocessor%2Fbatch%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Aprocessor%2Fbatch) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aclosed%20label%3Aprocessor%2Fbatch%20&label=closed&color=blue&logo=opentelemetry)](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 | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aopen%20label%3Aprocessor%2Fmemorylimiter%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Aprocessor%2Fmemorylimiter) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aclosed%20label%3Aprocessor%2Fmemorylimiter%20&label=closed&color=blue&logo=opentelemetry)](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 | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aopen%20label%3Areceiver%2Fnop%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Areceiver%2Fnop) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aclosed%20label%3Areceiver%2Fnop%20&label=closed&color=blue&logo=opentelemetry)](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 | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aopen%20label%3Areceiver%2Fotlp%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Areceiver%2Fotlp) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aclosed%20label%3Areceiver%2Fotlp%20&label=closed&color=blue&logo=opentelemetry)](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 | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aopen%20label%3Apkg%2F%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Apkg%2F) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aclosed%20label%3Apkg%2F%20&label=closed&color=blue&logo=opentelemetry)](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 | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aopen%20label%3Apkg%2Fscraperhelper%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Apkg%2Fscraperhelper) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aclosed%20label%3Apkg%2Fscraperhelper%20&label=closed&color=blue&logo=opentelemetry)](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 | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aopen%20label%3Apkg%2Fservice%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aopen+is%3Aissue+label%3Apkg%2Fservice) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector?query=is%3Aissue%20is%3Aclosed%20label%3Apkg%2Fservice%20&label=closed&color=blue&logo=opentelemetry)](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 -}}
{{.Name}}
{{- else -}}
{{.Name}}
{{- end -}} ================================================ FILE: service/internal/zpages/templates/extensions_table.html ================================================ {{range $rowindex, $row := .Rows}} {{- if even $rowindex}} {{else}} {{end -}} {{end}}
{{.FullName}}
================================================ FILE: service/internal/zpages/templates/features_table.html ================================================ {{range $rowindex, $row := .Rows}} {{- if even $rowindex}} {{else}} {{end -}} {{end}}
ID   |   Enabled   |   Description   |   Stage   |   From Version   |   To Version   |   Reference URL
{{$row.ID}}  |   {{$row.Enabled}}  |   {{$row.Description}}  |   {{$row.Stage}}  |   {{$row.FromVersion}}  |   {{$row.ToVersion}}  |   {{$row.ReferenceURL}}  |  
================================================ FILE: service/internal/zpages/templates/page_footer.html ================================================ ================================================ FILE: service/internal/zpages/templates/page_header.html ================================================ {{.Title}}

{{.Title}}

================================================ FILE: service/internal/zpages/templates/pipelines_table.html ================================================ {{range $rowindex, $row := .Rows}} {{- if even $rowindex}} {{else}} {{end -}} {{end}}
FullName   |   InputType   |   MutatesData   |   Receivers   |   Processors   |   Exporters
{{$row.FullName}}  |   {{$row.InputType}}  |   {{$row.MutatesData}}  |   {{range $recindex, $rec := $row.Receivers}} {{$rec}}
{{end}}
  |   → {{range $proindex, $pro := $row.Processors}} {{$pro}} → {{end}}   |   {{range $expindex, $exp := $row.Exporters}} {{$exp}}
{{end}}
================================================ FILE: service/internal/zpages/templates/properties_table.html ================================================ {{.Name}}: {{ $index := 0 }} {{range $index, $element := .Properties}} {{- if even $index}} {{else}} {{end -}} {{end}}
{{$element|getKey}}   |   {{$element|getValue}}
================================================ FILE: service/internal/zpages/templates.go ================================================ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package zpages // import "go.opentelemetry.io/collector/service/internal/zpages" import ( _ "embed" "html/template" "io" "log" ) var ( templateFunctions = template.FuncMap{ "even": even, "getKey": getKey, "getValue": getValue, } //go:embed templates/component_header.html componentHeaderBytes []byte componentHeaderTemplate = parseTemplate("component_header", componentHeaderBytes) //go:embed templates/extensions_table.html extensionsTableBytes []byte extensionsTableTemplate = parseTemplate("extensions_table", extensionsTableBytes) //go:embed templates/page_header.html headerBytes []byte headerTemplate = parseTemplate("header", headerBytes) //go:embed templates/page_footer.html footerBytes []byte footerTemplate = parseTemplate("footer", footerBytes) //go:embed templates/pipelines_table.html pipelinesTableBytes []byte pipelinesTableTemplate = parseTemplate("pipelines_table", pipelinesTableBytes) //go:embed templates/properties_table.html propertiesTableBytes []byte propertiesTableTemplate = parseTemplate("properties_table", propertiesTableBytes) //go:embed templates/features_table.html featuresTableBytes []byte featuresTableTemplate = parseTemplate("features_table", featuresTableBytes) ) func parseTemplate(name string, bytes []byte) *template.Template { return template.Must(template.New(name).Funcs(templateFunctions).Parse(string(bytes))) } // HeaderData contains data for the header template. type HeaderData struct { Title string } // WriteHTMLPageHeader writes the header. func WriteHTMLPageHeader(w io.Writer, hd HeaderData) { if err := headerTemplate.Execute(w, hd); err != nil { log.Printf("zpages: executing template: %v", err) } } // SummaryExtensionsTableData contains data for extensions summary table template. type SummaryExtensionsTableData struct { Rows []SummaryExtensionsTableRowData } // SummaryExtensionsTableRowData contains data for one row in extensions summary table template. type SummaryExtensionsTableRowData struct { FullName string Enabled bool } // WriteHTMLExtensionsSummaryTable writes the summary table for one component type (receivers, processors, exporters). // Id does not write the header or footer. func WriteHTMLExtensionsSummaryTable(w io.Writer, spd SummaryExtensionsTableData) { if err := extensionsTableTemplate.Execute(w, spd); err != nil { log.Printf("zpages: executing template: %v", err) } } // SummaryPipelinesTableData contains data for pipelines summary table template. type SummaryPipelinesTableData struct { Rows []SummaryPipelinesTableRowData } // SummaryPipelinesTableRowData contains data for one row in pipelines summary table template. type SummaryPipelinesTableRowData struct { FullName string InputType string MutatesData bool Receivers []string Processors []string Exporters []string } // WriteHTMLPipelinesSummaryTable writes the summary table for one component type (receivers, processors, exporters). // Id does not write the header or footer. func WriteHTMLPipelinesSummaryTable(w io.Writer, spd SummaryPipelinesTableData) { if err := pipelinesTableTemplate.Execute(w, spd); err != nil { log.Printf("zpages: executing template: %v", err) } } // ComponentHeaderData contains data for component header template. type ComponentHeaderData struct { Name string ComponentEndpoint string Link bool } // WriteHTMLComponentHeader writes the header for components. func WriteHTMLComponentHeader(w io.Writer, chd ComponentHeaderData) { if err := componentHeaderTemplate.Execute(w, chd); err != nil { log.Printf("zpages: executing template: %v", err) } } // PropertiesTableData contains data for properties table template. type PropertiesTableData struct { Name string Properties [][2]string } // WriteHTMLPropertiesTable writes the HTML for properties table. func WriteHTMLPropertiesTable(w io.Writer, chd PropertiesTableData) { if err := propertiesTableTemplate.Execute(w, chd); err != nil { log.Printf("zpages: executing template: %v", err) } } // WriteHTMLPageFooter writes the footer. func WriteHTMLPageFooter(w io.Writer) { if err := footerTemplate.Execute(w, nil); err != nil { log.Printf("zpages: executing template: %v", err) } } func even(x int) bool { return x%2 == 0 } func getKey(row [2]string) string { return row[0] } func getValue(row [2]string) string { return row[1] } // FeatureGateTableData contains data for feature gate table template. type FeatureGateTableData struct { Rows []FeatureGateTableRowData } // FeatureGateTableRowData contains data for one row in feature gate table template. type FeatureGateTableRowData struct { ID string Enabled bool Description string Stage string FromVersion string ToVersion string ReferenceURL string } // WriteHTMLFeaturesTable writes a table summarizing registered feature gates. func WriteHTMLFeaturesTable(w io.Writer, ftd FeatureGateTableData) { if err := featuresTableTemplate.Execute(w, ftd); err != nil { log.Printf("zpages: executing template: %v", err) } } ================================================ FILE: service/internal/zpages/templates_test.go ================================================ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package zpages import ( "bytes" "html/template" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) const tmplBody = `

{{.Index|even}}

{{.Element|getKey}}

{{.Element|getValue}}

` const want = `

true

key

value

` type testFuncsInput struct { Index int Element [2]string } var tmpl = template.Must(template.New("countTest").Funcs(templateFunctions).Parse(tmplBody)) func TestTemplateFuncs(t *testing.T) { buf := new(bytes.Buffer) input := testFuncsInput{ Index: 32, Element: [2]string{"key", "value"}, } require.NoError(t, tmpl.Execute(buf, input)) assert.Equal(t, want, buf.String()) } func TestNoCrash(t *testing.T) { buf := new(bytes.Buffer) assert.NotPanics(t, func() { WriteHTMLPageHeader(buf, HeaderData{Title: "Foo"}) }) assert.NotPanics(t, func() { WriteHTMLComponentHeader(buf, ComponentHeaderData{Name: "Bar"}) }) assert.NotPanics(t, func() { WriteHTMLComponentHeader(buf, ComponentHeaderData{Name: "Bar", ComponentEndpoint: "pagez", Link: true}) }) assert.NotPanics(t, func() { WriteHTMLPipelinesSummaryTable(buf, SummaryPipelinesTableData{ Rows: []SummaryPipelinesTableRowData{{ FullName: "test", InputType: "metrics", MutatesData: false, Receivers: []string{"oc"}, Processors: []string{"nop"}, Exporters: []string{"oc"}, }}, }) }) assert.NotPanics(t, func() { WriteHTMLExtensionsSummaryTable(buf, SummaryExtensionsTableData{ Rows: []SummaryExtensionsTableRowData{{ FullName: "test", }}, }) }) assert.NotPanics(t, func() { WriteHTMLPropertiesTable(buf, PropertiesTableData{Name: "Bar", Properties: [][2]string{{"key", "value"}}}) }) assert.NotPanics(t, func() { WriteHTMLFeaturesTable(buf, FeatureGateTableData{Rows: []FeatureGateTableRowData{ { ID: "test", Enabled: false, Description: "test gate", }, }}) }) assert.NotPanics(t, func() { WriteHTMLPageFooter(buf) }) assert.NotPanics(t, func() { WriteHTMLPageFooter(buf) }) } ================================================ FILE: service/metadata.yaml ================================================ display_name: Service type: service github_project: open-telemetry/opentelemetry-collector status: disable_codecov_badge: true class: pkg stability: development: [traces, metrics, logs, profiles] distributions: [core, contrib] telemetry: metrics: connector.consumed.items: prefix: otelcol. enabled: true stability: development description: Number of items passed to the connector. unit: "{item}" sum: value_type: int monotonic: true connector.consumed.size: prefix: otelcol. enabled: false stability: development description: Size of items passed to the connector, based on ProtoMarshaler.Sizer. unit: "{item}" sum: value_type: int monotonic: true connector.produced.items: prefix: otelcol. enabled: true stability: development description: Number of items emitted from the connector. unit: "{item}" sum: value_type: int monotonic: true connector.produced.size: prefix: otelcol. enabled: false stability: development description: Size of items emitted from the connector, based on ProtoMarshaler.Sizer. unit: "{item}" sum: value_type: int monotonic: true exporter.consumed.items: prefix: otelcol. enabled: true stability: development description: Number of items passed to the exporter. unit: "{item}" sum: value_type: int monotonic: true exporter.consumed.size: prefix: otelcol. enabled: false stability: development description: Size of items passed to the exporter, based on ProtoMarshaler.Sizer. unit: "{item}" sum: value_type: int monotonic: true process_cpu_seconds: enabled: true stability: alpha description: Total CPU user and system time in seconds unit: s sum: async: true value_type: double monotonic: true process_memory_rss: enabled: true stability: alpha description: Total physical memory (resident set size) unit: By gauge: async: true value_type: int process_runtime_heap_alloc_bytes: enabled: true stability: alpha description: Bytes of allocated heap objects (see 'go doc runtime.MemStats.HeapAlloc') unit: By gauge: async: true value_type: int process_runtime_total_alloc_bytes: enabled: true stability: alpha description: Cumulative bytes allocated for heap objects (see 'go doc runtime.MemStats.TotalAlloc') unit: By sum: async: true value_type: int monotonic: true process_runtime_total_sys_memory_bytes: enabled: true stability: alpha description: Total bytes of memory obtained from the OS (see 'go doc runtime.MemStats.Sys') unit: By gauge: async: true value_type: int process_uptime: enabled: true stability: alpha description: Uptime of the process unit: s sum: async: true value_type: double monotonic: true processor.consumed.items: prefix: otelcol. enabled: true stability: development description: Number of items passed to the processor. unit: "{item}" sum: value_type: int monotonic: true processor.consumed.size: prefix: otelcol. enabled: false stability: development description: Size of items passed to the processor, based on ProtoMarshaler.Sizer. unit: "{item}" sum: value_type: int monotonic: true processor.produced.items: prefix: otelcol. enabled: true stability: development description: Number of items emitted from the processor. unit: "{item}" sum: value_type: int monotonic: true processor.produced.size: prefix: otelcol. enabled: false stability: development description: Size of items emitted from the processor, based on ProtoMarshaler.Sizer. unit: "{item}" sum: value_type: int monotonic: true receiver.produced.items: prefix: otelcol. enabled: true stability: development description: Number of items emitted from the receiver. unit: "{item}" sum: value_type: int monotonic: true receiver.produced.size: prefix: otelcol. enabled: false stability: development description: Size of items emitted from the receiver, based on ProtoMarshaler.Sizer. unit: "{item}" sum: value_type: int monotonic: true feature_gates: - id: service.AllowNoPipelines description: 'Allow starting the Collector without starting any pipelines.' stage: alpha from_version: 'v0.122.0' reference_url: 'https://github.com/open-telemetry/opentelemetry-collector/pull/12613' - id: service.profilesSupport description: 'Controls whether profiles support can be enabled' stage: alpha from_version: 'v0.112.0' reference_url: 'https://github.com/open-telemetry/opentelemetry-collector/pull/11477' - id: telemetry.UseLocalHostAsDefaultMetricsAddress description: 'Controls whether default Prometheus metrics server use localhost as the default host for their endpoints' stage: beta from_version: 'v0.111.0' reference_url: 'https://github.com/open-telemetry/opentelemetry-collector/pull/11251' - id: telemetry.newPipelineTelemetry description: 'Injects component-identifying scope attributes in internal Collector metrics' stage: alpha from_version: 'v0.123.0' reference_url: 'https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/rfcs/component-universal-telemetry.md' ================================================ FILE: service/pipelines/config.go ================================================ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package pipelines // import "go.opentelemetry.io/collector/service/pipelines" import ( "errors" "fmt" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/pipeline" "go.opentelemetry.io/collector/pipeline/xpipeline" "go.opentelemetry.io/collector/service/internal/metadata" ) var ( errMissingServicePipelines = errors.New("service must have at least one pipeline") errMissingServicePipelineReceivers = errors.New("must have at least one receiver") errMissingServicePipelineExporters = errors.New("must have at least one exporter") AllowNoPipelines = metadata.ServiceAllowNoPipelinesFeatureGate ) // Config defines the configurable settings for service telemetry. type Config map[pipeline.ID]*PipelineConfig func (cfg Config) Validate() error { // Must have at least one pipeline unless explicitly disabled. if !metadata.ServiceAllowNoPipelinesFeatureGate.IsEnabled() && len(cfg) == 0 { return errMissingServicePipelines } if !metadata.ServiceProfilesSupportFeatureGate.IsEnabled() { // Check that all pipelines have at least one receiver and one exporter, and they reference // only configured components. for pipelineID := range cfg { if pipelineID.Signal() == xpipeline.SignalProfiles { return fmt.Errorf( "pipeline %q: profiling signal support is at alpha level, gated under the %q feature gate", pipelineID.String(), metadata.ServiceProfilesSupportFeatureGate.ID(), ) } } } return nil } // PipelineConfig defines the configuration of a Pipeline. type PipelineConfig struct { Receivers []component.ID `mapstructure:"receivers"` Processors []component.ID `mapstructure:"processors"` Exporters []component.ID `mapstructure:"exporters"` } func (cfg *PipelineConfig) Validate() error { // Validate pipeline has at least one receiver. if len(cfg.Receivers) == 0 { return errMissingServicePipelineReceivers } // Validate pipeline has at least one exporter. if len(cfg.Exporters) == 0 { return errMissingServicePipelineExporters } // Validate no processors are duplicated within a pipeline. procSet := make(map[component.ID]struct{}, len(cfg.Processors)) for _, ref := range cfg.Processors { // Ensure no processors are duplicated within the pipeline if _, exists := procSet[ref]; exists { return fmt.Errorf("references processor %q multiple times", ref) } procSet[ref] = struct{}{} } return nil } ================================================ FILE: service/pipelines/config_test.go ================================================ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package pipelines import ( "errors" "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/pipeline/xpipeline" "go.opentelemetry.io/collector/service/internal/metadata" ) func TestConfigValidate(t *testing.T) { testCases := []struct { name string // test case name (also file name containing config yaml) cfgFn func(*testing.T) Config expected error }{ { name: "valid", cfgFn: generateConfig, expected: nil, }, { name: "duplicate-processor-reference", cfgFn: func(*testing.T) Config { cfg := generateConfig(t) pipe := cfg[pipeline.NewID(pipeline.SignalTraces)] pipe.Processors = append(pipe.Processors, pipe.Processors...) return cfg }, expected: errors.New(`references processor "nop" multiple times`), }, { name: "missing-pipeline-receivers", cfgFn: func(*testing.T) Config { cfg := generateConfig(t) cfg[pipeline.NewID(pipeline.SignalTraces)].Receivers = nil return cfg }, expected: errMissingServicePipelineReceivers, }, { name: "missing-pipeline-exporters", cfgFn: func(*testing.T) Config { cfg := generateConfig(t) cfg[pipeline.NewID(pipeline.SignalTraces)].Exporters = nil return cfg }, expected: errMissingServicePipelineExporters, }, { name: "missing-pipelines", cfgFn: func(*testing.T) Config { return nil }, expected: errMissingServicePipelines, }, { name: "disabled-featuregate-profiles", cfgFn: func(*testing.T) Config { cfg := generateConfig(t) cfg[pipeline.NewID(xpipeline.SignalProfiles)] = &PipelineConfig{ Receivers: []component.ID{component.MustNewID("nop")}, Processors: []component.ID{component.MustNewID("nop")}, Exporters: []component.ID{component.MustNewID("nop")}, } return cfg }, expected: errors.New(`profiling signal support is at alpha level, gated under the "service.profilesSupport" feature gate`), }, { name: "enabled-featuregate-profiles", cfgFn: func(t *testing.T) Config { require.NoError(t, featuregate.GlobalRegistry().Set(metadata.ServiceProfilesSupportFeatureGate.ID(), true)) cfg := generateConfig(t) cfg[pipeline.NewID(xpipeline.SignalProfiles)] = &PipelineConfig{ Receivers: []component.ID{component.MustNewID("nop")}, Processors: []component.ID{component.MustNewID("nop")}, Exporters: []component.ID{component.MustNewID("nop")}, } return cfg }, expected: nil, }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { cfg := tt.cfgFn(t) if tt.expected != nil { require.ErrorContains(t, xconfmap.Validate(cfg), tt.expected.Error()) } else { require.NoError(t, xconfmap.Validate(cfg)) } // Clean up the profiles support gate, which may have been enabled in `cfgFn`. require.NoError(t, featuregate.GlobalRegistry().Set(metadata.ServiceProfilesSupportFeatureGate.ID(), false)) }) } } func TestNoPipelinesFeatureGate(t *testing.T) { cfg := Config{} require.Error(t, xconfmap.Validate(cfg)) gate := 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(t *testing.T) Config { t.Helper() return map[pipeline.ID]*PipelineConfig{ pipeline.NewID(pipeline.SignalTraces): { Receivers: []component.ID{component.MustNewID("nop")}, Processors: []component.ID{component.MustNewID("nop")}, Exporters: []component.ID{component.MustNewID("nop")}, }, } } ================================================ FILE: service/pipelines/package_test.go ================================================ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package pipelines import ( "testing" "go.uber.org/goleak" ) func TestMain(m *testing.M) { goleak.VerifyTestMain(m) } ================================================ FILE: service/service.go ================================================ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 //go:generate mdatagen metadata.yaml package service // import "go.opentelemetry.io/collector/service" import ( "context" "errors" "fmt" "runtime" noopmetric "go.opentelemetry.io/otel/metric/noop" nooptrace "go.opentelemetry.io/otel/trace/noop" "go.uber.org/multierr" "go.uber.org/zap" "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/pdata/pcommon" "go.opentelemetry.io/collector/processor" "go.opentelemetry.io/collector/receiver" "go.opentelemetry.io/collector/service/extensions" "go.opentelemetry.io/collector/service/internal/builders" "go.opentelemetry.io/collector/service/internal/graph" "go.opentelemetry.io/collector/service/internal/metricviews" "go.opentelemetry.io/collector/service/internal/moduleinfo" "go.opentelemetry.io/collector/service/internal/proctelemetry" "go.opentelemetry.io/collector/service/internal/status" "go.opentelemetry.io/collector/service/telemetry" ) // ModuleInfo describes the Go module for a particular component. type ModuleInfo = moduleinfo.ModuleInfo // ModuleInfos describes the go module for all components. type ModuleInfos = moduleinfo.ModuleInfos // Settings holds configuration for building a new Service. type Settings struct { // BuildInfo provides collector start information. BuildInfo component.BuildInfo // CollectorConf contains the Collector's current configuration CollectorConf *confmap.Conf // Receivers configuration to its builder. ReceiversConfigs map[component.ID]component.Config ReceiversFactories map[component.Type]receiver.Factory // Processors configuration to its builder. ProcessorsConfigs map[component.ID]component.Config ProcessorsFactories map[component.Type]processor.Factory // exporters configuration to its builder. ExportersConfigs map[component.ID]component.Config ExportersFactories map[component.Type]exporter.Factory // Connectors configuration to its builder. ConnectorsConfigs map[component.ID]component.Config ConnectorsFactories map[component.Type]connector.Factory // Extensions builder for extensions. Extensions builders.Extension // Extensions configuration to its builder. ExtensionsConfigs map[component.ID]component.Config ExtensionsFactories map[component.Type]extension.Factory // ModuleInfo describes the go module for each component. ModuleInfos ModuleInfos // AsyncErrorChannel is the channel that is used to report fatal errors. AsyncErrorChannel chan error // LoggingOptions provides a way to change behavior of zap logging. // // These options will be appended to any options passed to BuildZapLogger. // // Deprecated [v0.142.0]: use BuildZapLogger instead. This field will be // removed in the future, and options must be injected through BuildZapLogger. LoggingOptions []zap.Option // BuildZapLogger holds an optional function for creating a Zap logger from // a zap.Config and options. If this is unspecified, zap.Config.Build will // be used. // // NOTE: in the future this field will be required. BuildZapLogger func(zap.Config, ...zap.Option) (*zap.Logger, error) // TelemetryFactory is the factory for creating internal telemetry providers. TelemetryFactory telemetry.Factory } // Service represents the implementation of a component.Host. type Service struct { buildInfo component.BuildInfo telemetrySettings component.TelemetrySettings host *graph.Host collectorConf *confmap.Conf loggerShutdownFunc component.ShutdownFunc meterProvider telemetry.MeterProvider tracerProvider telemetry.TracerProvider } // New creates a new Service, its telemetry, and Components. func New(ctx context.Context, set Settings, cfg Config) (_ *Service, resultErr error) { srv := &Service{ buildInfo: set.BuildInfo, host: &graph.Host{ Receivers: builders.NewReceiver(set.ReceiversConfigs, set.ReceiversFactories), Processors: builders.NewProcessor(set.ProcessorsConfigs, set.ProcessorsFactories), Exporters: builders.NewExporter(set.ExportersConfigs, set.ExportersFactories), Connectors: builders.NewConnector(set.ConnectorsConfigs, set.ConnectorsFactories), Extensions: builders.NewExtension(set.ExtensionsConfigs, set.ExtensionsFactories), ModuleInfos: set.ModuleInfos, BuildInfo: set.BuildInfo, AsyncErrorChannel: set.AsyncErrorChannel, }, collectorConf: set.CollectorConf, } if set.TelemetryFactory == nil { return nil, errors.New("telemetry factory not provided") } // Create the resource first. This ensures all telemetry providers // (logger, meter, tracer) use the same resource with a consistent service.instance.id. telemetrySettings := telemetry.Settings{BuildInfo: set.BuildInfo} resource, err := set.TelemetryFactory.CreateResource(ctx, telemetrySettings, cfg.Telemetry) if err != nil { return nil, fmt.Errorf("failed to create resource: %w", err) } telemetrySettings.Resource = &resource // Create a function for telemetry providers to build the Zap logger. // This injects any LoggingOptions specified in the Settings. buildZapLogger := set.BuildZapLogger if buildZapLogger == nil { buildZapLogger = zap.Config.Build } if len(set.LoggingOptions) > 0 { origBuildZapLogger := buildZapLogger buildZapLogger = func(cfg zap.Config, opts ...zap.Option) (*zap.Logger, error) { opts = append(opts, set.LoggingOptions...) return origBuildZapLogger(cfg, opts...) } } loggerSettings := telemetry.LoggerSettings{ Settings: telemetrySettings, BuildZapLogger: buildZapLogger, } logger, loggerShutdownFunc, err := set.TelemetryFactory.CreateLogger(ctx, loggerSettings, cfg.Telemetry) if err != nil { return nil, fmt.Errorf("failed to create logger: %w", err) } defer func() { if resultErr != nil { logger.Error("error found during service initialization", zap.Error(resultErr)) resultErr = multierr.Append(resultErr, loggerShutdownFunc.Shutdown(ctx)) } }() srv.loggerShutdownFunc = loggerShutdownFunc meterSettings := telemetry.MeterSettings{ Settings: telemetrySettings, Logger: logger, DefaultViews: metricviews.DefaultViews, } meterProvider, err := set.TelemetryFactory.CreateMeterProvider(ctx, meterSettings, cfg.Telemetry) if err != nil { return nil, fmt.Errorf("failed to create meter provider: %w", err) } defer func() { if resultErr != nil { resultErr = multierr.Append(resultErr, meterProvider.Shutdown(ctx)) } }() srv.meterProvider = meterProvider tracerSettings := telemetry.TracerSettings{ Settings: telemetrySettings, Logger: logger, } tracerProvider, err := set.TelemetryFactory.CreateTracerProvider(ctx, tracerSettings, cfg.Telemetry) if err != nil { return nil, fmt.Errorf("failed to create tracer provider: %w", err) } defer func() { if resultErr != nil { resultErr = multierr.Append(resultErr, tracerProvider.Shutdown(ctx)) } }() srv.tracerProvider = tracerProvider srv.telemetrySettings = component.TelemetrySettings{ Logger: logger, MeterProvider: meterProvider, TracerProvider: tracerProvider, Resource: resource, } srv.host.Reporter = status.NewReporter(srv.host.NotifyComponentStatusChange, func(err error) { if errors.Is(err, status.ErrStatusNotReady) { logger.Warn("Invalid transition", zap.Error(err)) } // ignore other errors as they represent invalid state transitions and are considered benign. }) err = srv.initGraph(ctx, cfg) if err != nil { return nil, err } // process the configuration and initialize the pipeline err = srv.initExtensions(ctx, cfg.Extensions) if err != nil { return nil, err } // Register process metrics on supported OSes. if err := registerProcessMetrics(srv, runtime.GOOS, proctelemetry.RegisterProcessMetrics); err != nil { return nil, err } return srv, nil } // Start starts the extensions and pipelines. If Start fails Shutdown should be called to ensure a clean state. // Start does the following steps in order: // 1. Start all extensions. // 2. Notify extensions about Collector configuration // 3. Start all pipelines. // 4. Notify extensions that the pipeline is ready. func (srv *Service) Start(ctx context.Context) error { srv.telemetrySettings.Logger.Info("Starting "+srv.buildInfo.Command+"...", zap.String("Version", srv.buildInfo.Version), zap.Int("NumCPU", runtime.NumCPU()), ) if err := srv.host.ServiceExtensions.Start(ctx, srv.host); err != nil { return fmt.Errorf("failed to start extensions: %w", err) } if srv.collectorConf != nil { if err := srv.host.ServiceExtensions.NotifyConfig(ctx, srv.collectorConf); err != nil { return err } } if err := srv.host.Pipelines.StartAll(ctx, srv.host); err != nil { return fmt.Errorf("cannot start pipelines: %w", err) } if err := srv.host.ServiceExtensions.NotifyPipelineReady(); err != nil { return err } srv.telemetrySettings.Logger.Info("Everything is ready. Begin running and processing data.") return nil } // Shutdown the service. Shutdown will do the following steps in order: // 1. Notify extensions that the pipeline is shutting down. // 2. Shutdown all pipelines. // 3. Shutdown all extensions. // 4. Shutdown telemetry. func (srv *Service) Shutdown(ctx context.Context) error { // Accumulate errors and proceed with shutting down remaining components. var errs error // Begin shutdown sequence. srv.telemetrySettings.Logger.Info("Starting shutdown...") if err := srv.host.ServiceExtensions.NotifyPipelineNotReady(); err != nil { errs = multierr.Append(errs, fmt.Errorf("failed to notify that pipeline is not ready: %w", err)) } if err := srv.host.Pipelines.ShutdownAll(ctx, srv.host.Reporter); err != nil { errs = multierr.Append(errs, fmt.Errorf("failed to shutdown pipelines: %w", err)) } if err := srv.host.ServiceExtensions.Shutdown(ctx); err != nil { errs = multierr.Append(errs, fmt.Errorf("failed to shutdown extensions: %w", err)) } srv.telemetrySettings.Logger.Info("Shutdown complete.") // Shut down telemetry providers in the reverse order of creation, // since the tracer and meter providers may use the logger. if err := srv.tracerProvider.Shutdown(ctx); err != nil { errs = multierr.Append(errs, fmt.Errorf("failed to shutdown tracer provider: %w", err)) } if err := srv.meterProvider.Shutdown(ctx); err != nil { errs = multierr.Append(errs, fmt.Errorf("failed to shutdown meter provider: %w", err)) } if err := srv.loggerShutdownFunc.Shutdown(ctx); err != nil { errs = multierr.Append(errs, fmt.Errorf("failed to shutdown logger: %w", err)) } return errs } // Creates extensions. func (srv *Service) initExtensions(ctx context.Context, cfg extensions.Config) error { var err error extensionsSettings := extensions.Settings{ Telemetry: srv.telemetrySettings, BuildInfo: srv.buildInfo, Extensions: srv.host.Extensions, } if srv.host.ServiceExtensions, err = extensions.New(ctx, extensionsSettings, cfg, extensions.WithReporter(srv.host.Reporter)); err != nil { return fmt.Errorf("failed to build extensions: %w", err) } return nil } // Creates the pipeline graph. func (srv *Service) initGraph(ctx context.Context, cfg Config) error { var err error if srv.host.Pipelines, err = graph.Build(ctx, graph.Settings{ Telemetry: srv.telemetrySettings, BuildInfo: srv.buildInfo, ReceiverBuilder: srv.host.Receivers, ProcessorBuilder: srv.host.Processors, ExporterBuilder: srv.host.Exporters, ConnectorBuilder: srv.host.Connectors, PipelineConfigs: cfg.Pipelines, ReportStatus: srv.host.Reporter.ReportStatus, }); err != nil { return fmt.Errorf("failed to build pipelines: %w", err) } return nil } // Logger returns the logger created for this service. // This is a temporary API that may be removed soon after investigating how the collector should record different events. func (srv *Service) Logger() *zap.Logger { return srv.telemetrySettings.Logger } // Validate verifies the graph by calling the internal graph.Build. func Validate(ctx context.Context, set Settings, cfg Config) error { tel := component.TelemetrySettings{ Logger: zap.NewNop(), TracerProvider: nooptrace.NewTracerProvider(), MeterProvider: noopmetric.NewMeterProvider(), Resource: pcommon.NewResource(), } _, err := graph.Build(ctx, graph.Settings{ Telemetry: tel, BuildInfo: set.BuildInfo, ReceiverBuilder: builders.NewReceiver(set.ReceiversConfigs, set.ReceiversFactories), ProcessorBuilder: builders.NewProcessor(set.ProcessorsConfigs, set.ProcessorsFactories), ExporterBuilder: builders.NewExporter(set.ExportersConfigs, set.ExportersFactories), ConnectorBuilder: builders.NewConnector(set.ConnectorsConfigs, set.ConnectorsFactories), PipelineConfigs: cfg.Pipelines, }) if err != nil { return fmt.Errorf("failed to build pipelines: %w", err) } return nil } // registerProcessMetrics registers process metrics on supported operating systems. // // Historically, attempting to register process metrics on unsupported platforms // (e.g. AIX) caused the Collector to fail at startup. // See https://github.com/open-telemetry/opentelemetry-collector/issues/12098 func registerProcessMetrics( srv *Service, goos string, register func(component.TelemetrySettings, ...proctelemetry.RegisterOption) error, ) error { switch goos { // Only support the OSes that we explicitly test and build for, // plus others that are known to have some support in gopsutil. case "linux", "darwin", "windows", "freebsd", "openbsd", "solaris", "plan9": if err := register(srv.telemetrySettings); err != nil { return fmt.Errorf("failed to register process metrics: %w", err) } default: // On unsupported OSes, log a warning and continue startup. srv.telemetrySettings.Logger.Warn( "Process metrics are disabled on this operating system", zap.String("os", goos), ) } return nil } ================================================ FILE: service/service_host_test.go ================================================ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package service import ( "context" "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/pipeline" "go.opentelemetry.io/collector/pipeline/xpipeline" ) var wrongType = component.MustNewType("wrong") func TestService_Host_GetFactory(t *testing.T) { set := newNopSettings() srv, err := New(context.Background(), set, newNopConfig()) require.NoError(t, err) assert.NoError(t, srv.Start(context.Background())) t.Cleanup(func() { assert.NoError(t, srv.Shutdown(context.Background())) }) assert.Nil(t, srv.host.GetFactory(component.KindReceiver, wrongType)) assert.Equal(t, srv.host.Receivers.Factory(nopType), srv.host.GetFactory(component.KindReceiver, nopType)) assert.Nil(t, srv.host.GetFactory(component.KindProcessor, wrongType)) assert.Equal(t, srv.host.Processors.Factory(nopType), srv.host.GetFactory(component.KindProcessor, nopType)) assert.Nil(t, srv.host.GetFactory(component.KindExporter, wrongType)) assert.Equal(t, srv.host.Exporters.Factory(nopType), srv.host.GetFactory(component.KindExporter, nopType)) assert.Nil(t, srv.host.GetFactory(component.KindConnector, wrongType)) assert.Equal(t, srv.host.Connectors.Factory(nopType), srv.host.GetFactory(component.KindConnector, nopType)) assert.Nil(t, srv.host.GetFactory(component.KindExtension, wrongType)) assert.Equal(t, srv.host.Extensions.Factory(nopType), srv.host.GetFactory(component.KindExtension, nopType)) // Try retrieve non existing component.Kind. assert.Nil(t, srv.host.GetFactory(component.Kind{}, nopType)) } func TestService_Host_GetExtensions(t *testing.T) { srv, err := New(context.Background(), newNopSettings(), newNopConfig()) require.NoError(t, err) assert.NoError(t, srv.Start(context.Background())) t.Cleanup(func() { assert.NoError(t, srv.Shutdown(context.Background())) }) extMap := srv.host.GetExtensions() assert.Len(t, extMap, 1) assert.Contains(t, extMap, component.NewID(nopType)) } func TestService_Host_GetExporters(t *testing.T) { srv, err := New(context.Background(), newNopSettings(), newNopConfig()) require.NoError(t, err) assert.NoError(t, srv.Start(context.Background())) t.Cleanup(func() { assert.NoError(t, srv.Shutdown(context.Background())) }) //nolint:staticcheck expMap := srv.host.GetExporters() v, ok := expMap[pipeline.SignalTraces] assert.True(t, ok) assert.NotNil(t, v) assert.Len(t, expMap, 4) assert.Len(t, expMap[pipeline.SignalTraces], 1) assert.Contains(t, expMap[pipeline.SignalTraces], component.NewID(nopType)) assert.Len(t, expMap[pipeline.SignalMetrics], 1) assert.Contains(t, expMap[pipeline.SignalMetrics], component.NewID(nopType)) assert.Len(t, expMap[pipeline.SignalLogs], 1) assert.Contains(t, expMap[pipeline.SignalLogs], component.NewID(nopType)) assert.Len(t, expMap[xpipeline.SignalProfiles], 1) assert.Contains(t, expMap[xpipeline.SignalProfiles], component.NewID(nopType)) } func TestService_Host_FatalError(t *testing.T) { set := newNopSettings() set.AsyncErrorChannel = make(chan error) srv, err := New(context.Background(), set, newNopConfig()) require.NoError(t, err) assert.NoError(t, srv.Start(context.Background())) t.Cleanup(func() { assert.NoError(t, srv.Shutdown(context.Background())) }) go func() { ev := componentstatus.NewFatalErrorEvent(assert.AnError) srv.host.NotifyComponentStatusChange(&componentstatus.InstanceID{}, ev) }() err = <-srv.host.AsyncErrorChannel require.ErrorIs(t, err, assert.AnError) } ================================================ FILE: service/service_test.go ================================================ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package service import ( "context" "errors" "net/http" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" otelconf "go.opentelemetry.io/contrib/otelconf/v0.3.0" noopmetric "go.opentelemetry.io/otel/metric/noop" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" sdktrace "go.opentelemetry.io/otel/sdk/trace" nooptrace "go.opentelemetry.io/otel/trace/noop" "go.uber.org/zap" "go.uber.org/zap/zapcore" "go.uber.org/zap/zaptest/observer" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/config/confighttp" "go.opentelemetry.io/collector/config/confignet" "go.opentelemetry.io/collector/config/configtelemetry" "go.opentelemetry.io/collector/confmap" "go.opentelemetry.io/collector/extension" "go.opentelemetry.io/collector/extension/zpagesextension" "go.opentelemetry.io/collector/internal/testutil" "go.opentelemetry.io/collector/pdata/pcommon" "go.opentelemetry.io/collector/pipeline" "go.opentelemetry.io/collector/pipeline/xpipeline" "go.opentelemetry.io/collector/service/extensions" "go.opentelemetry.io/collector/service/internal/builders" "go.opentelemetry.io/collector/service/internal/proctelemetry" "go.opentelemetry.io/collector/service/pipelines" "go.opentelemetry.io/collector/service/telemetry" "go.opentelemetry.io/collector/service/telemetry/telemetrytest" ) const ( otelCommand = "otelcoltest" ) var nopType = component.MustNewType("nop") // TestServiceTelemetryCleanupOnError tests that if newService errors due to an invalid config telemetry is cleaned up // and another service with a valid config can be started right after. func TestServiceTelemetryCleanupOnError(t *testing.T) { invalidCfg := newNopConfig() invalidCfg.Pipelines[pipeline.NewID(pipeline.SignalTraces)].Processors[0] = component.MustNewID("invalid") // Create a service with an invalid config and expect an error _, err := New(context.Background(), newNopSettings(), invalidCfg) require.Error(t, err) // Create a service with a valid config and expect no error srv, err := New(context.Background(), newNopSettings(), newNopConfig()) require.NoError(t, err) assert.NoError(t, srv.Shutdown(context.Background())) } func TestServiceTelemetryLogging(t *testing.T) { observerCore, observedLogs := observer.New(zapcore.WarnLevel) zapLogger := zap.New(observerCore) set := newNopSettings() set.BuildInfo = component.BuildInfo{Version: "test version", Command: otelCommand} set.TelemetryFactory = telemetry.NewFactory( func() component.Config { return nil }, telemetrytest.WithLogger(zapLogger, nil), ) cfg := newNopConfig() srv, err := New(context.Background(), set, cfg) require.NoError(t, err) require.NoError(t, srv.Start(context.Background())) defer func() { assert.NoError(t, srv.Shutdown(context.Background())) }() require.NotNil(t, srv.telemetrySettings.Logger) assert.Equal(t, srv.telemetrySettings.Logger, srv.Logger()) assert.Equal(t, zapcore.WarnLevel, srv.telemetrySettings.Logger.Level()) srv.telemetrySettings.Logger.Warn("warn_message") srv.telemetrySettings.Logger.Info("info_message") entries := observedLogs.All() require.Len(t, entries, 1) assert.Equal(t, "warn_message", entries[0].Message) } func TestServiceTelemetryLogging_Settings(t *testing.T) { observerCore, observedLogs := observer.New(zapcore.WarnLevel) zapConfig := zap.Config{Encoding: "foo"} set := newNopSettings() set.BuildZapLogger = func(cfg zap.Config, opts ...zap.Option) (*zap.Logger, error) { require.Equal(t, zapConfig, cfg) return zap.New(observerCore, opts...), nil } set.LoggingOptions = []zap.Option{zap.Fields(zap.String("extra.field", "value"))} set.BuildInfo = component.BuildInfo{Version: "test version", Command: otelCommand} set.TelemetryFactory = telemetry.NewFactory( func() component.Config { return nil }, telemetry.WithCreateLogger( func(_ context.Context, set telemetry.LoggerSettings, _ component.Config) ( *zap.Logger, component.ShutdownFunc, error, ) { require.NotNil(t, set.BuildZapLogger) require.Empty(t, set.ZapOptions) logger, err := set.BuildZapLogger(zapConfig) return logger, nil, err }, ), ) cfg := newNopConfig() srv, err := New(context.Background(), set, cfg) require.NoError(t, err) require.NoError(t, srv.Start(context.Background())) defer func() { assert.NoError(t, srv.Shutdown(context.Background())) }() require.NotNil(t, srv.telemetrySettings.Logger) assert.Equal(t, srv.telemetrySettings.Logger, srv.Logger()) assert.Equal(t, zapcore.WarnLevel, srv.telemetrySettings.Logger.Level()) srv.telemetrySettings.Logger.Warn("warn_message") entries := observedLogs.All() require.Len(t, entries, 1) assert.Contains(t, entries[0].ContextMap(), "extra.field") } func TestServiceTelemetryMetrics(t *testing.T) { // Start a service and check that metrics are produced as expected. // We do this twice to ensure that the server is stopped cleanly. for range 2 { reader := metric.NewManualReader() set := newNopSettings() set.TelemetryFactory = telemetry.NewFactory( func() component.Config { return nil }, telemetrytest.WithMeterProvider( metric.NewMeterProvider( metric.WithReader(reader), ), ), ) srv, err := New(context.Background(), set, newNopConfig()) require.NoError(t, err) require.NoError(t, srv.Start(context.Background())) var rm metricdata.ResourceMetrics err = reader.Collect(context.Background(), &rm) require.NoError(t, err) assertMetrics(t, rm) require.NoError(t, srv.Shutdown(context.Background())) } } func assertMetrics(t *testing.T, rm metricdata.ResourceMetrics) { require.Len(t, rm.ScopeMetrics, 1) assert.Equal(t, "go.opentelemetry.io/collector/service", rm.ScopeMetrics[0].Scope.Name) actualNames := make([]string, len(rm.ScopeMetrics[0].Metrics)) for i, m := range rm.ScopeMetrics[0].Metrics { actualNames[i] = m.Name } assert.ElementsMatch(t, []string{ "otelcol_process_cpu_seconds", "otelcol_process_memory_rss", "otelcol_process_runtime_heap_alloc_bytes", "otelcol_process_runtime_total_alloc_bytes", "otelcol_process_runtime_total_sys_memory_bytes", "otelcol_process_uptime", }, actualNames) } func TestServiceTelemetryDefaultViews(t *testing.T) { var views []otelconf.View set := newNopSettings() set.TelemetryFactory = telemetry.NewFactory( func() component.Config { return nil }, telemetry.WithCreateMeterProvider( func(_ context.Context, set telemetry.MeterSettings, _ component.Config) (telemetry.MeterProvider, error) { views = set.DefaultViews(configtelemetry.LevelBasic) return telemetrytest.ShutdownMeterProvider{ MeterProvider: noopmetric.NewMeterProvider(), }, nil }, ), ) srv, err := New(context.Background(), set, newNopConfig()) require.NoError(t, err) require.NoError(t, srv.Start(context.Background())) defer func() { assert.NoError(t, srv.Shutdown(context.Background())) }() require.NotEmpty(t, views) } // TestServiceTelemetryZPages verifies that the zpages extension works correctly with servce telemetry. func TestServiceTelemetryZPages(t *testing.T) { t.Run("ipv4", func(t *testing.T) { testZPages(t, testutil.GetAvailableLocalAddress(t)) }) t.Run("ipv6", func(t *testing.T) { testZPages(t, testutil.GetAvailableLocalIPv6Address(t)) }) } func testZPages(t *testing.T, zpagesAddr string) { set := newNopSettings() set.BuildInfo = component.BuildInfo{Version: "test version", Command: otelCommand} set.ExtensionsConfigs = map[component.ID]component.Config{ component.MustNewID("zpages"): &zpagesextension.Config{ ServerConfig: confighttp.ServerConfig{ NetAddr: confignet.AddrConfig{ Endpoint: zpagesAddr, Transport: confignet.TransportTypeTCP, }, }, }, } set.ExtensionsFactories = map[component.Type]extension.Factory{ component.MustNewType("zpages"): zpagesextension.NewFactory(), } cfg := newNopConfig() cfg.Extensions = []component.ID{component.MustNewID("zpages")} // The zpages extension will register/unregister a span processor with // the tracer provider if it implements the RegisterSpanProcessor and // UnregisterSpanProcessor methods of the opentelemetry-go SDK implementation. // Hence we use sdktrace below, rather than the noop tracer provider. set.TelemetryFactory = telemetry.NewFactory( func() component.Config { return nil }, telemetrytest.WithTracerProvider(sdktrace.NewTracerProvider()), ) // Start a service and check that zpages is healthy. // We do this twice to ensure that the server is stopped cleanly. for range 2 { srv, err := New(context.Background(), set, cfg) require.NoError(t, err) require.NoError(t, srv.Start(context.Background())) assert.Eventually(t, func() bool { return zpagesHealthy(zpagesAddr) }, 10*time.Second, 100*time.Millisecond, "zpages endpoint is not healthy") require.NoError(t, srv.Shutdown(context.Background())) } } func zpagesHealthy(zpagesAddr string) bool { paths := []string{ "/debug/tracez", "/debug/pipelinez", "/debug/servicez", "/debug/extensionz", } for _, path := range paths { resp, err := http.Get("http://" + zpagesAddr + path) if err != nil { return false } if resp.Body.Close() != nil { return false } if resp.StatusCode != http.StatusOK { return false } } return true } // TestServiceTelemetryRestart tests that the service starts and shuts down telemetry as expected. func TestServiceTelemetryRestart(t *testing.T) { telemetryCreated := make(chan struct{}, 1) telemetryShutdown := make(chan struct{}, 1) set := newNopSettings() set.TelemetryFactory = telemetry.NewFactory( func() component.Config { return nil }, telemetry.WithCreateTracerProvider( func(context.Context, telemetry.TracerSettings, component.Config) (telemetry.TracerProvider, error) { telemetryCreated <- struct{}{} return telemetrytest.ShutdownTracerProvider{ TracerProvider: nooptrace.NewTracerProvider(), ShutdownFunc: func(context.Context) error { telemetryShutdown <- struct{}{} return nil }, }, nil }, ), ) for range 2 { // Create and start a service, telemetry should be created. srv, err := New(context.Background(), set, newNopConfig()) require.NoError(t, err) require.NoError(t, srv.Start(context.Background())) <-telemetryCreated // Shutdown the service, telemetry should be shutdown. require.NoError(t, srv.Shutdown(context.Background())) <-telemetryShutdown } } func TestServiceTelemetryShutdownError(t *testing.T) { set := newNopSettings() set.TelemetryFactory = telemetry.NewFactory( func() component.Config { return nil }, telemetry.WithCreateLogger( func(context.Context, telemetry.LoggerSettings, component.Config) (*zap.Logger, component.ShutdownFunc, error) { return zap.NewNop(), func(context.Context) error { return errors.New("an exception occurred") }, nil }, ), telemetry.WithCreateMeterProvider( func(context.Context, telemetry.MeterSettings, component.Config) (telemetry.MeterProvider, error) { return telemetrytest.ShutdownMeterProvider{ MeterProvider: noopmetric.NewMeterProvider(), ShutdownFunc: func(context.Context) error { return errors.New("an exception occurred") }, }, nil }, ), telemetry.WithCreateTracerProvider( func(context.Context, telemetry.TracerSettings, component.Config) (telemetry.TracerProvider, error) { return telemetrytest.ShutdownTracerProvider{ TracerProvider: nooptrace.NewTracerProvider(), ShutdownFunc: func(context.Context) error { return errors.New("an exception occurred") }, }, nil }, ), ) // Create and start a service cfg := newNopConfig() srv, err := New(context.Background(), set, cfg) require.NoError(t, err) require.NoError(t, srv.Start(context.Background())) // Shutdown the service err = srv.Shutdown(context.Background()) assert.EqualError(t, err, ""+ "failed to shutdown tracer provider: an exception occurred; "+ "failed to shutdown meter provider: an exception occurred; "+ "failed to shutdown logger: an exception occurred", ) } func TestExtensionNotificationFailure(t *testing.T) { set := newNopSettings() cfg := newNopConfig() extName := component.MustNewType("configWatcher") configWatcherExtensionFactory := newConfigWatcherExtensionFactory(extName) set.ExtensionsConfigs = map[component.ID]component.Config{component.NewID(extName): configWatcherExtensionFactory.CreateDefaultConfig()} set.ExtensionsFactories = map[component.Type]extension.Factory{extName: configWatcherExtensionFactory} cfg.Extensions = []component.ID{component.NewID(extName)} // Create a service srv, err := New(context.Background(), set, cfg) require.NoError(t, err) // Start the service require.Error(t, srv.Start(context.Background())) // Shut down the service require.NoError(t, srv.Shutdown(context.Background())) } func TestNilCollectorEffectiveConfig(t *testing.T) { set := newNopSettings() set.CollectorConf = nil cfg := newNopConfig() extName := component.MustNewType("configWatcher") configWatcherExtensionFactory := newConfigWatcherExtensionFactory(extName) set.ExtensionsConfigs = map[component.ID]component.Config{component.NewID(extName): configWatcherExtensionFactory.CreateDefaultConfig()} set.ExtensionsFactories = map[component.Type]extension.Factory{extName: configWatcherExtensionFactory} cfg.Extensions = []component.ID{component.NewID(extName)} // Create a service srv, err := New(context.Background(), set, cfg) require.NoError(t, err) // Start the service require.NoError(t, srv.Start(context.Background())) // Shut down the service require.NoError(t, srv.Shutdown(context.Background())) } func TestServiceTelemetryLogger(t *testing.T) { srv, err := New(context.Background(), newNopSettings(), newNopConfig()) require.NoError(t, err) assert.NoError(t, srv.Start(context.Background())) t.Cleanup(func() { assert.NoError(t, srv.Shutdown(context.Background())) }) assert.NotNil(t, srv.telemetrySettings.Logger) } func TestServiceTelemetryCreateProvidersError(t *testing.T) { loggerOpt := telemetry.WithCreateLogger( func(context.Context, telemetry.LoggerSettings, component.Config) (*zap.Logger, component.ShutdownFunc, error) { return nil, nil, errors.New("something went wrong") }, ) meterOpt := telemetry.WithCreateMeterProvider( func(context.Context, telemetry.MeterSettings, component.Config) (telemetry.MeterProvider, error) { return nil, errors.New("something went wrong") }, ) tracerOpt := telemetry.WithCreateTracerProvider( func(context.Context, telemetry.TracerSettings, component.Config) (telemetry.TracerProvider, error) { return nil, errors.New("something went wrong") }, ) resourceOpt := telemetry.WithCreateResource( func(context.Context, telemetry.Settings, component.Config) (pcommon.Resource, error) { return pcommon.Resource{}, errors.New("something went wrong") }, ) type testcase struct { opts []telemetry.FactoryOption expectedErr string } for name, tc := range map[string]testcase{ "CreateLogger": { opts: []telemetry.FactoryOption{loggerOpt, meterOpt, tracerOpt}, expectedErr: "failed to create logger: something went wrong", }, "CreateMeterProvider": { opts: []telemetry.FactoryOption{meterOpt, tracerOpt}, expectedErr: "failed to create meter provider: something went wrong", }, "CreateTracerProvider": { opts: []telemetry.FactoryOption{tracerOpt}, expectedErr: "failed to create tracer provider: something went wrong", }, "CreateResource": { opts: []telemetry.FactoryOption{resourceOpt}, expectedErr: "failed to create resource: something went wrong", }, } { t.Run(name, func(t *testing.T) { set := newNopSettings() set.TelemetryFactory = telemetry.NewFactory(func() component.Config { return nil }, tc.opts...) _, err := New(context.Background(), set, newNopConfig()) require.EqualError(t, err, tc.expectedErr) }) } } func TestNew_NilTelemetryProvider(t *testing.T) { set := newNopSettings() set.TelemetryFactory = nil _, err := New(context.Background(), set, newNopConfig()) require.EqualError(t, err, "telemetry factory not provided") } func newNopSettings() Settings { receiversConfigs, receiversFactories := builders.NewNopReceiverConfigsAndFactories() processorsConfigs, processorsFactories := builders.NewNopProcessorConfigsAndFactories() connectorsConfigs, connectorsFactories := builders.NewNopConnectorConfigsAndFactories() exportersConfigs, exportersFactories := builders.NewNopExporterConfigsAndFactories() extensionsConfigs, extensionsFactories := builders.NewNopExtensionConfigsAndFactories() telemetryFactory := telemetry.NewFactory(func() component.Config { return nil }) return Settings{ BuildInfo: component.NewDefaultBuildInfo(), CollectorConf: confmap.New(), ReceiversConfigs: receiversConfigs, ReceiversFactories: receiversFactories, ProcessorsConfigs: processorsConfigs, ProcessorsFactories: processorsFactories, ExportersConfigs: exportersConfigs, ExportersFactories: exportersFactories, ConnectorsConfigs: connectorsConfigs, ConnectorsFactories: connectorsFactories, ExtensionsConfigs: extensionsConfigs, ExtensionsFactories: extensionsFactories, AsyncErrorChannel: make(chan error), TelemetryFactory: telemetryFactory, } } func newNopConfig() Config { return newNopConfigPipelineConfigs(pipelines.Config{ pipeline.NewID(pipeline.SignalTraces): { Receivers: []component.ID{component.NewID(nopType)}, Processors: []component.ID{component.NewID(nopType)}, Exporters: []component.ID{component.NewID(nopType)}, }, pipeline.NewID(pipeline.SignalMetrics): { Receivers: []component.ID{component.NewID(nopType)}, Processors: []component.ID{component.NewID(nopType)}, Exporters: []component.ID{component.NewID(nopType)}, }, pipeline.NewID(pipeline.SignalLogs): { Receivers: []component.ID{component.NewID(nopType)}, Processors: []component.ID{component.NewID(nopType)}, Exporters: []component.ID{component.NewID(nopType)}, }, pipeline.NewID(xpipeline.SignalProfiles): { Receivers: []component.ID{component.NewID(nopType)}, Processors: []component.ID{component.NewID(nopType)}, Exporters: []component.ID{component.NewID(nopType)}, }, }) } func newNopConfigPipelineConfigs(pipelineCfgs pipelines.Config) Config { return Config{ Extensions: extensions.Config{component.NewID(nopType)}, Pipelines: pipelineCfgs, } } type configWatcherExtension struct{} func (comp *configWatcherExtension) Start(context.Context, component.Host) error { return nil } func (comp *configWatcherExtension) Shutdown(context.Context) error { return nil } func (comp *configWatcherExtension) NotifyConfig(context.Context, *confmap.Conf) error { return errors.New("Failed to resolve config") } func newConfigWatcherExtensionFactory(name component.Type) extension.Factory { return extension.NewFactory( name, func() component.Config { return &struct{}{} }, func(context.Context, extension.Settings, component.Config) (extension.Extension, error) { return &configWatcherExtension{}, nil }, component.StabilityLevelDevelopment, ) } func TestValidateGraph(t *testing.T) { testCases := map[string]struct { connectorCfg map[component.ID]component.Config receiverCfg map[component.ID]component.Config exporterCfg map[component.ID]component.Config pipelinesCfg pipelines.Config expectedError string }{ "Valid connector usage": { connectorCfg: map[component.ID]component.Config{ component.NewIDWithName(nopType, "connector1"): &struct{}{}, }, receiverCfg: map[component.ID]component.Config{ component.NewID(nopType): &struct{}{}, }, exporterCfg: map[component.ID]component.Config{ component.NewID(nopType): &struct{}{}, }, pipelinesCfg: pipelines.Config{ pipeline.NewIDWithName(pipeline.SignalLogs, "in"): { Receivers: []component.ID{component.NewID(nopType)}, Processors: []component.ID{}, Exporters: []component.ID{component.NewIDWithName(nopType, "connector1")}, }, pipeline.NewIDWithName(pipeline.SignalLogs, "out"): { Receivers: []component.ID{component.NewIDWithName(nopType, "connector1")}, Processors: []component.ID{}, Exporters: []component.ID{component.NewID(nopType)}, }, }, expectedError: "", }, "Valid without Connector": { receiverCfg: map[component.ID]component.Config{ component.NewID(nopType): &struct{}{}, }, exporterCfg: map[component.ID]component.Config{ component.NewID(nopType): &struct{}{}, }, pipelinesCfg: pipelines.Config{ pipeline.NewIDWithName(pipeline.SignalLogs, "in"): { Receivers: []component.ID{component.NewID(nopType)}, Processors: []component.ID{}, Exporters: []component.ID{component.NewID(nopType)}, }, pipeline.NewIDWithName(pipeline.SignalLogs, "out"): { Receivers: []component.ID{component.NewID(nopType)}, Processors: []component.ID{}, Exporters: []component.ID{component.NewID(nopType)}, }, }, expectedError: "", }, "Connector used as exporter but not as receiver": { connectorCfg: map[component.ID]component.Config{ component.NewIDWithName(nopType, "connector1"): &struct{}{}, }, receiverCfg: map[component.ID]component.Config{ component.NewID(nopType): &struct{}{}, }, exporterCfg: map[component.ID]component.Config{ component.NewID(nopType): &struct{}{}, }, pipelinesCfg: pipelines.Config{ pipeline.NewIDWithName(pipeline.SignalLogs, "in1"): { Receivers: []component.ID{component.NewID(nopType)}, Processors: []component.ID{}, Exporters: []component.ID{component.NewID(nopType)}, }, pipeline.NewIDWithName(pipeline.SignalLogs, "in2"): { Receivers: []component.ID{component.NewID(nopType)}, Processors: []component.ID{}, Exporters: []component.ID{component.NewIDWithName(nopType, "connector1")}, }, pipeline.NewIDWithName(pipeline.SignalLogs, "out"): { Receivers: []component.ID{component.NewID(nopType)}, Processors: []component.ID{}, Exporters: []component.ID{component.NewID(nopType)}, }, }, expectedError: `failed to build pipelines: connector "nop/connector1" used as exporter in [logs/in2] pipeline but not used in any supported receiver pipeline`, }, "Connector used as receiver but not as exporter": { connectorCfg: map[component.ID]component.Config{ component.NewIDWithName(nopType, "connector1"): &struct{}{}, }, receiverCfg: map[component.ID]component.Config{ component.NewID(nopType): &struct{}{}, }, exporterCfg: map[component.ID]component.Config{ component.NewID(nopType): &struct{}{}, }, pipelinesCfg: pipelines.Config{ pipeline.NewIDWithName(pipeline.SignalLogs, "in1"): { Receivers: []component.ID{component.NewID(nopType)}, Processors: []component.ID{}, Exporters: []component.ID{component.NewID(nopType)}, }, pipeline.NewIDWithName(pipeline.SignalLogs, "in2"): { Receivers: []component.ID{component.NewIDWithName(nopType, "connector1")}, Processors: []component.ID{}, Exporters: []component.ID{component.NewID(nopType)}, }, pipeline.NewIDWithName(pipeline.SignalLogs, "out"): { Receivers: []component.ID{component.NewID(nopType)}, Processors: []component.ID{}, Exporters: []component.ID{component.NewID(nopType)}, }, }, expectedError: `failed to build pipelines: connector "nop/connector1" used as receiver in [logs/in2] pipeline but not used in any supported exporter pipeline`, }, "Connector creates direct cycle between pipelines": { connectorCfg: map[component.ID]component.Config{ component.NewIDWithName(nopType, "forward"): &struct{}{}, }, receiverCfg: map[component.ID]component.Config{ component.NewID(nopType): &struct{}{}, }, exporterCfg: map[component.ID]component.Config{ component.NewID(nopType): &struct{}{}, }, pipelinesCfg: pipelines.Config{ pipeline.NewIDWithName(pipeline.SignalTraces, "in"): { Receivers: []component.ID{component.NewIDWithName(nopType, "forward")}, Processors: []component.ID{}, Exporters: []component.ID{component.NewIDWithName(nopType, "forward")}, }, pipeline.NewIDWithName(pipeline.SignalTraces, "out"): { Receivers: []component.ID{component.NewIDWithName(nopType, "forward")}, Processors: []component.ID{}, Exporters: []component.ID{component.NewIDWithName(nopType, "forward")}, }, }, expectedError: `failed to build pipelines: cycle detected: connector "nop/forward" (traces to traces) -> connector "nop/forward" (traces to traces)`, }, } _, connectorsFactories := builders.NewNopConnectorConfigsAndFactories() _, receiversFactories := builders.NewNopReceiverConfigsAndFactories() _, exportersFactories := builders.NewNopExporterConfigsAndFactories() for name, tc := range testCases { t.Run(name, func(t *testing.T) { settings := Settings{ ConnectorsConfigs: tc.connectorCfg, ConnectorsFactories: connectorsFactories, ReceiversConfigs: tc.receiverCfg, ReceiversFactories: receiversFactories, ExportersConfigs: tc.exporterCfg, ExportersFactories: exportersFactories, } cfg := Config{ Pipelines: tc.pipelinesCfg, } err := Validate(context.Background(), settings, cfg) if tc.expectedError == "" { require.NoError(t, err) } else { require.Error(t, err) assert.Equal(t, tc.expectedError, err.Error()) } }) } } func TestRegisterProcessMetrics_UnsupportedOS_Warns(t *testing.T) { mockRegister := func(_ component.TelemetrySettings, _ ...proctelemetry.RegisterOption) error { t.Fatalf("should not be called on unsupported OS") return nil } core, logs := observer.New(zapcore.WarnLevel) logger := zap.New(core) srv := &Service{ telemetrySettings: component.TelemetrySettings{Logger: logger}, } err := registerProcessMetrics(srv, "aix", mockRegister) require.NoError(t, err) require.Equal(t, 1, logs.Len(), "Expected exactly one warning log") entry := logs.All()[0] require.Equal(t, "Process metrics are disabled on this operating system", entry.Message) require.Equal(t, "aix", entry.ContextMap()["os"], "Log should contain the OS field") } func TestRegisterProcessMetrics_SupportedOS_CallsRegister(t *testing.T) { called := false mockRegister := func(_ component.TelemetrySettings, _ ...proctelemetry.RegisterOption) error { called = true return nil } srv := &Service{ telemetrySettings: component.TelemetrySettings{Logger: zap.NewNop()}, } err := registerProcessMetrics(srv, "linux", mockRegister) require.NoError(t, err) require.True(t, called, "Registration function should be called on supported OS") } func TestRegisterProcessMetrics_SupportedOS_RegisterFails_ReturnsError(t *testing.T) { wantErr := errors.New("boom") mockRegister := func(_ component.TelemetrySettings, _ ...proctelemetry.RegisterOption) error { return wantErr } srv := &Service{ telemetrySettings: component.TelemetrySettings{Logger: zap.NewNop()}, } err := registerProcessMetrics(srv, "linux", mockRegister) require.Error(t, err) require.ErrorIs(t, err, wantErr) require.Contains(t, err.Error(), "failed to register process metrics") } ================================================ FILE: service/telemetry/doc.go ================================================ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package telemetry provides an abstract interface for creating // telemetry providers. package telemetry // import "go.opentelemetry.io/collector/service/telemetry" ================================================ FILE: service/telemetry/otelconftelemetry/config.go ================================================ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconftelemetry // import "go.opentelemetry.io/collector/service/telemetry/otelconftelemetry" import ( "errors" "go.opentelemetry.io/collector/config/configtelemetry" "go.opentelemetry.io/collector/service/telemetry/otelconftelemetry/internal/migration" ) // Config defines the configurable settings for service telemetry. type Config struct { Logs LogsConfig `mapstructure:"logs"` Metrics MetricsConfig `mapstructure:"metrics"` Traces TracesConfig `mapstructure:"traces,omitempty"` // Resource specifies user-defined attributes to include with all emitted telemetry. // Note that some attributes are added automatically (e.g. service.version) even // if they are not specified here. In order to suppress such attributes the // attribute must be specified in this map with null YAML value (nil string pointer). Resource map[string]*string `mapstructure:"resource,omitempty"` } // LogsConfig defines the configurable settings for service telemetry logs. // This MUST be compatible with zap.Config. Cannot use directly zap.Config because // the collector uses mapstructure and not yaml tags. type LogsConfig = migration.LogsConfigV030 // LogsSamplingConfig sets a sampling strategy for the logger. Sampling caps the // global CPU and I/O load that logging puts on your process while attempting // to preserve a representative subset of your logs. type LogsSamplingConfig = migration.LogsSamplingConfig // MetricsConfig exposes the common Telemetry configuration for one component. // Experimental: *NOTE* this structure is subject to change or removal in the future. type MetricsConfig = migration.MetricsConfigV030 // TracesConfig exposes the common Telemetry configuration for collector's internal spans. // Experimental: *NOTE* this structure is subject to change or removal in the future. type TracesConfig = migration.TracesConfigV030 // Validate checks whether the current configuration is valid func (c *Config) Validate() error { // Check when service telemetry metric level is not none, the metrics readers should not be empty if c.Metrics.Level != configtelemetry.LevelNone && len(c.Metrics.Readers) == 0 { return errors.New("collector telemetry metrics reader should exist when metric level is not none") } if c.Metrics.Views != nil && c.Metrics.Level != configtelemetry.LevelDetailed { return errors.New("service::telemetry::metrics::views can only be set when service::telemetry::metrics::level is detailed") } return nil } ================================================ FILE: service/telemetry/otelconftelemetry/config_test.go ================================================ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconftelemetry import ( "path/filepath" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" config "go.opentelemetry.io/contrib/otelconf/v0.3.0" "go.uber.org/zap" "go.opentelemetry.io/collector/component/componenttest" "go.opentelemetry.io/collector/config/configtelemetry" "go.opentelemetry.io/collector/confmap" "go.opentelemetry.io/collector/confmap/confmaptest" "go.opentelemetry.io/collector/confmap/xconfmap" ) func TestComponentConfigStruct(t *testing.T) { require.NoError(t, componenttest.CheckConfigStruct( NewFactory().CreateDefaultConfig(), )) } func TestUnmarshalDefaultConfig(t *testing.T) { factory := NewFactory() cfg := factory.CreateDefaultConfig() require.NoError(t, confmap.New().Unmarshal(&cfg)) assert.Equal(t, factory.CreateDefaultConfig(), cfg) } func TestDefaultConfig_DoesNotEnableResourceConstantLabels(t *testing.T) { cfg := createDefaultConfig().(*Config) require.Len(t, cfg.Metrics.Readers, 1) require.NotNil(t, cfg.Metrics.Readers[0].Pull) require.NotNil(t, cfg.Metrics.Readers[0].Pull.Exporter.Prometheus) assert.Nil(t, cfg.Metrics.Readers[0].Pull.Exporter.Prometheus.WithResourceConstantLabels) } func TestConfig(t *testing.T) { t.Parallel() type testcase struct { setup func(*testing.T) // optional testcase setup config *Config unmarshalErr string validateErr string } tests := map[string]testcase{ "config_empty.yaml": { config: createDefaultConfig().(*Config), }, "config_logs.yaml": { config: func() *Config { cfg := createDefaultConfig().(*Config) cfg.Logs.Development = true cfg.Logs.DisableCaller = true cfg.Logs.DisableStacktrace = true cfg.Logs.InitialFields = map[string]any{"fieldKey": "fieldValue"} cfg.Logs.Level = zap.InfoLevel cfg.Logs.Sampling = &LogsSamplingConfig{ Enabled: false, Tick: 1 * time.Second, Initial: 234, Thereafter: 567, } cfg.Logs.Processors = []config.LogRecordProcessor{{ Batch: &config.BatchLogRecordProcessor{ Exporter: config.LogRecordExporter{ Console: config.Console{}, }, }, }} return cfg }(), }, "config_metrics_empty_readers.yaml": { config: func() *Config { cfg := createDefaultConfig().(*Config) cfg.Metrics.Level = configtelemetry.LevelNone cfg.Metrics.Readers = []config.MetricReader{} return cfg }(), }, "config_invalid_unknown_field.yaml": { unmarshalErr: `invalid keys: unknown`, }, "config_invalid_metrics_empty_readers.yaml": { validateErr: `collector telemetry metrics reader should exist when metric level is not none`, }, "config_invalid_metrics_views_level.yaml": { validateErr: `service::telemetry::metrics::views can only be set when service::telemetry::metrics::level is detailed`, }, } for filename, test := range tests { t.Run(filename, func(t *testing.T) { if test.setup != nil { test.setup(t) } cm, err := confmaptest.LoadConf(filepath.Join("testdata", filename)) require.NoError(t, err) cfg := createDefaultConfig().(*Config) err = cm.Unmarshal(cfg) if test.unmarshalErr != "" { assert.ErrorContains(t, err, test.unmarshalErr) return } require.NoError(t, err) err = xconfmap.Validate(cfg) if test.validateErr != "" { assert.ErrorContains(t, err, test.validateErr) return } require.NoError(t, err) assert.Equal(t, test.config, cfg) }) } } ================================================ FILE: service/telemetry/otelconftelemetry/factory.go ================================================ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconftelemetry // import "go.opentelemetry.io/collector/service/telemetry/otelconftelemetry" import ( "time" config "go.opentelemetry.io/contrib/otelconf/v0.3.0" "go.uber.org/zap/zapcore" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/config/configtelemetry" "go.opentelemetry.io/collector/service/internal/metadata" "go.opentelemetry.io/collector/service/telemetry" ) // NewFactory creates a new telemetry.Factory that uses otelconf // to configure opentelemetry-go SDK telemetry providers. func NewFactory() telemetry.Factory { return telemetry.NewFactory( createDefaultConfig, telemetry.WithCreateResource(createResource), telemetry.WithCreateLogger(createLogger), telemetry.WithCreateMeterProvider(createMeterProvider), telemetry.WithCreateTracerProvider(createTracerProvider), ) } func createDefaultConfig() component.Config { metricsHost := "localhost" if !metadata.TelemetryUseLocalHostAsDefaultMetricsAddressFeatureGate.IsEnabled() { metricsHost = "" } return &Config{ Logs: LogsConfig{ Level: zapcore.InfoLevel, Development: false, Encoding: "console", Sampling: &LogsSamplingConfig{ Enabled: true, Tick: 10 * time.Second, Initial: 10, Thereafter: 100, }, OutputPaths: []string{"stderr"}, ErrorOutputPaths: []string{"stderr"}, DisableCaller: false, DisableStacktrace: false, InitialFields: map[string]any(nil), DisableZapResource: false, }, Metrics: MetricsConfig{ Level: configtelemetry.LevelNormal, MeterProvider: config.MeterProvider{ Readers: []config.MetricReader{ { Pull: &config.PullMetricReader{Exporter: config.PullMetricExporter{Prometheus: &config.Prometheus{ WithoutScopeInfo: ptr(true), WithoutUnits: ptr(true), WithoutTypeSuffix: ptr(true), Host: &metricsHost, Port: ptr(8888), }}}, }, }, }, }, } } func ptr[T any](v T) *T { return &v } ================================================ FILE: service/telemetry/otelconftelemetry/factory_test.go ================================================ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconftelemetry import ( "strconv" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/collector/featuregate" "go.opentelemetry.io/collector/service/internal/metadata" ) func TestDefaultConfig(t *testing.T) { tests := []struct { expected string gate bool }{ { expected: "localhost", gate: true, }, { expected: "", gate: false, }, } for _, tt := range tests { t.Run("UseLocalHostAsDefaultMetricsAddress/"+strconv.FormatBool(tt.gate), func(t *testing.T) { prev := metadata.TelemetryUseLocalHostAsDefaultMetricsAddressFeatureGate.IsEnabled() require.NoError(t, featuregate.GlobalRegistry().Set(metadata.TelemetryUseLocalHostAsDefaultMetricsAddressFeatureGate.ID(), tt.gate)) defer func() { // Restore previous value. require.NoError(t, featuregate.GlobalRegistry().Set(metadata.TelemetryUseLocalHostAsDefaultMetricsAddressFeatureGate.ID(), prev)) }() cfg := NewFactory().CreateDefaultConfig() require.Len(t, cfg.(*Config).Metrics.Readers, 1) assert.Equal(t, tt.expected, *cfg.(*Config).Metrics.Readers[0].Pull.Exporter.Prometheus.Host) assert.Equal(t, 8888, *cfg.(*Config).Metrics.Readers[0].Pull.Exporter.Prometheus.Port) }) } } ================================================ FILE: service/telemetry/otelconftelemetry/internal/migration/normalize.go ================================================ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package migration // import "go.opentelemetry.io/collector/service/telemetry/otelconftelemetry/internal/migration" import "strings" func normalizeEndpoint(endpoint string, insecure *bool) *string { if !strings.HasPrefix(endpoint, "https://") && !strings.HasPrefix(endpoint, "http://") { if insecure != nil && *insecure { endpoint = "http://" + endpoint } else { endpoint = "https://" + endpoint } } return &endpoint } ================================================ FILE: service/telemetry/otelconftelemetry/internal/migration/testdata/v0.2.0_logs.yaml ================================================ level: "info" processors: - batch: exporter: otlp: protocol: http/protobuf endpoint: 127.0.0.1:4317 headers: "key1": "value1" - simple: exporter: console: {} - simple: exporter: otlp: protocol: http/protobuf endpoint: http://127.0.0.1:4317 headers: "key1": "value1" ================================================ FILE: service/telemetry/otelconftelemetry/internal/migration/testdata/v0.2.0_metrics.yaml ================================================ readers: - periodic: exporter: otlp: protocol: http/protobuf endpoint: 127.0.0.1:4317 headers: "key1": "value1" "key2": "value2" - pull: exporter: prometheus: host: 127.0.0.1 port: 8902 ================================================ FILE: service/telemetry/otelconftelemetry/internal/migration/testdata/v0.2.0_traces.yaml ================================================ processors: - batch: exporter: otlp: protocol: http/protobuf endpoint: 127.0.0.1:4317 headers: "key1": "value1" - simple: exporter: console: {} - simple: exporter: otlp: protocol: http/protobuf endpoint: http://127.0.0.1:4317 headers: "key1": "value1" ================================================ FILE: service/telemetry/otelconftelemetry/internal/migration/testdata/v0.3.0_logs.yaml ================================================ level: "info" processors: - batch: exporter: otlp: protocol: http/protobuf endpoint: 127.0.0.1:4317 headers: - name: "key1" value: "value1" - simple: exporter: console: {} - simple: exporter: otlp: protocol: http/protobuf endpoint: http://127.0.0.1:4317 headers: - name: "key1" value: "value1" ================================================ FILE: service/telemetry/otelconftelemetry/internal/migration/testdata/v0.3.0_metrics.yaml ================================================ level: detailed readers: - periodic: exporter: otlp: protocol: http/protobuf endpoint: 127.0.0.1:4317 headers: - name: "key1" value: "value1" - pull: exporter: prometheus: host: 127.0.0.1 port: 8902 ================================================ FILE: service/telemetry/otelconftelemetry/internal/migration/testdata/v0.3.0_traces.yaml ================================================ level: "none" processors: - batch: exporter: otlp: protocol: http/protobuf endpoint: 127.0.0.1:4317 headers: - name: "key1" value: "value1" - simple: exporter: console: {} - simple: exporter: otlp: protocol: http/protobuf endpoint: http://127.0.0.1:4317 headers: - name: "key1" value: "value1" sampler: parent_based: root: trace_id_ratio_based: ratio: 0.01 ================================================ FILE: service/telemetry/otelconftelemetry/internal/migration/v0.2.0.go ================================================ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package migration // import "go.opentelemetry.io/collector/service/telemetry/otelconftelemetry/internal/migration" import ( configv02 "go.opentelemetry.io/contrib/otelconf/v0.2.0" config "go.opentelemetry.io/contrib/otelconf/v0.3.0" "go.uber.org/zap/zapcore" "go.opentelemetry.io/collector/config/configtelemetry" ) type tracesConfigV020 struct { Level configtelemetry.Level `mapstructure:"level"` Propagators []string `mapstructure:"propagators"` Processors []configv02.SpanProcessor `mapstructure:"processors"` } type metricsConfigV020 struct { Level configtelemetry.Level `mapstructure:"level"` Readers []configv02.MetricReader `mapstructure:"readers"` } type logsConfigV020 struct { Level zapcore.Level `mapstructure:"level"` Development bool `mapstructure:"development"` Encoding string `mapstructure:"encoding"` DisableCaller bool `mapstructure:"disable_caller"` DisableStacktrace bool `mapstructure:"disable_stacktrace"` Sampling *LogsSamplingConfig `mapstructure:"sampling"` OutputPaths []string `mapstructure:"output_paths"` ErrorOutputPaths []string `mapstructure:"error_output_paths"` InitialFields map[string]any `mapstructure:"initial_fields"` Processors []configv02.LogRecordProcessor `mapstructure:"processors"` } func headersV02ToV03(in configv02.Headers) []config.NameStringValuePair { headers := make([]config.NameStringValuePair, 0, len(in)) for k, v := range in { headers = append(headers, config.NameStringValuePair{ Name: k, Value: &v, }) } return headers } func otlpV02ToV03(in *configv02.OTLP) *config.OTLP { if in == nil { return nil } return &config.OTLP{ Certificate: in.Certificate, ClientCertificate: in.ClientCertificate, ClientKey: in.ClientKey, Compression: in.Compression, Endpoint: normalizeEndpoint(in.Endpoint, in.Insecure), Insecure: in.Insecure, Protocol: &in.Protocol, Timeout: in.Timeout, Headers: headersV02ToV03(in.Headers), } } func otlpMetricV02ToV03(in *configv02.OTLPMetric) *config.OTLPMetric { if in == nil { return nil } return &config.OTLPMetric{ Certificate: in.Certificate, ClientCertificate: in.ClientCertificate, ClientKey: in.ClientKey, Compression: in.Compression, Endpoint: normalizeEndpoint(in.Endpoint, in.Insecure), Insecure: in.Insecure, Protocol: &in.Protocol, Timeout: in.Timeout, Headers: headersV02ToV03(in.Headers), } } func zipkinV02ToV03(in *configv02.Zipkin) *config.Zipkin { if in == nil { return nil } return &config.Zipkin{ Endpoint: &in.Endpoint, Timeout: in.Timeout, } } func tracesConfigV02ToV03(v2 tracesConfigV020, v3 *TracesConfigV030) error { processors := make([]config.SpanProcessor, len(v2.Processors)) for idx, p := range v2.Processors { processors[idx] = config.SpanProcessor{} if p.Batch != nil { processors[idx].Batch = &config.BatchSpanProcessor{ ExportTimeout: p.Batch.ExportTimeout, MaxExportBatchSize: p.Batch.MaxExportBatchSize, MaxQueueSize: p.Batch.MaxQueueSize, ScheduleDelay: p.Batch.ScheduleDelay, Exporter: config.SpanExporter{ Console: config.Console(p.Batch.Exporter.Console), OTLP: otlpV02ToV03(p.Batch.Exporter.OTLP), Zipkin: zipkinV02ToV03(p.Batch.Exporter.Zipkin), AdditionalProperties: p.Batch.Exporter.AdditionalProperties, }, } } if p.Simple != nil { processors[idx].Simple = &config.SimpleSpanProcessor{ Exporter: config.SpanExporter{ Console: config.Console(p.Simple.Exporter.Console), OTLP: otlpV02ToV03(p.Simple.Exporter.OTLP), Zipkin: zipkinV02ToV03(p.Simple.Exporter.Zipkin), AdditionalProperties: p.Simple.Exporter.AdditionalProperties, }, } } processors[idx].AdditionalProperties = p.AdditionalProperties } v3.Level = v2.Level v3.Propagators = v2.Propagators v3.Processors = processors return nil } func prometheusV02ToV03(in *configv02.Prometheus) *config.Prometheus { if in == nil { return nil } return &config.Prometheus{ Host: in.Host, Port: in.Port, WithoutScopeInfo: in.WithoutScopeInfo, WithoutUnits: in.WithoutUnits, WithoutTypeSuffix: in.WithoutTypeSuffix, WithResourceConstantLabels: (*config.IncludeExclude)(in.WithResourceConstantLabels), } } func metricsConfigV02ToV03(v2 metricsConfigV020, v3 *MetricsConfigV030) error { readers := make([]config.MetricReader, len(v2.Readers)) for idx, p := range v2.Readers { readers[idx] = config.MetricReader{} if p.Periodic != nil { readers[idx].Periodic = &config.PeriodicMetricReader{ Interval: p.Periodic.Interval, Timeout: p.Periodic.Timeout, Exporter: config.PushMetricExporter{ Console: config.Console(p.Periodic.Exporter.Console), OTLP: otlpMetricV02ToV03(p.Periodic.Exporter.OTLP), AdditionalProperties: p.Periodic.Exporter.AdditionalProperties, }, } } if p.Pull != nil { readers[idx].Pull = &config.PullMetricReader{ Exporter: config.PullMetricExporter{ Prometheus: prometheusV02ToV03(p.Pull.Exporter.Prometheus), AdditionalProperties: p.Pull.Exporter.AdditionalProperties, }, } } } v3.Level = v2.Level v3.Readers = readers return nil } func logsConfigV02ToV03(v2 logsConfigV020, v3 *LogsConfigV030) error { processors := make([]config.LogRecordProcessor, len(v2.Processors)) for idx, p := range v2.Processors { processors[idx] = config.LogRecordProcessor{} if p.Batch != nil { processors[idx].Batch = &config.BatchLogRecordProcessor{ ExportTimeout: p.Batch.ExportTimeout, MaxExportBatchSize: p.Batch.MaxExportBatchSize, MaxQueueSize: p.Batch.MaxQueueSize, ScheduleDelay: p.Batch.ScheduleDelay, Exporter: config.LogRecordExporter{ Console: config.Console(p.Batch.Exporter.Console), OTLP: otlpV02ToV03(p.Batch.Exporter.OTLP), AdditionalProperties: p.Batch.Exporter.AdditionalProperties, }, } } if p.Simple != nil { processors[idx].Simple = &config.SimpleLogRecordProcessor{ Exporter: config.LogRecordExporter{ Console: config.Console(p.Simple.Exporter.Console), OTLP: otlpV02ToV03(p.Simple.Exporter.OTLP), AdditionalProperties: p.Simple.Exporter.AdditionalProperties, }, } } processors[idx].AdditionalProperties = p.AdditionalProperties } v3.Level = v2.Level v3.Development = v2.Development v3.Encoding = v2.Encoding v3.DisableCaller = v2.DisableCaller v3.DisableStacktrace = v2.DisableStacktrace v3.Sampling = v2.Sampling v3.OutputPaths = v2.OutputPaths v3.ErrorOutputPaths = v2.ErrorOutputPaths v3.InitialFields = v2.InitialFields v3.Processors = processors return nil } ================================================ FILE: service/telemetry/otelconftelemetry/internal/migration/v0.2.0_test.go ================================================ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package migration import ( "path/filepath" "testing" "github.com/stretchr/testify/require" config "go.opentelemetry.io/contrib/otelconf/v0.3.0" "go.opentelemetry.io/collector/config/configtelemetry" "go.opentelemetry.io/collector/confmap/confmaptest" ) func TestUnmarshalLogsConfigV020(t *testing.T) { cm, err := confmaptest.LoadConf(filepath.Join("testdata", "v0.2.0_logs.yaml")) require.NoError(t, err) cfg := LogsConfigV030{ Encoding: "console", } require.NoError(t, cm.Unmarshal(&cfg)) require.Len(t, cfg.Processors, 3) // check the endpoint is prefixed w/ https require.Equal(t, "https://127.0.0.1:4317", *cfg.Processors[0].Batch.Exporter.OTLP.Endpoint) // check the endpoint is prefixed w/ http require.Equal(t, "http://127.0.0.1:4317", *cfg.Processors[2].Simple.Exporter.OTLP.Endpoint) // ensure defaults set in the original config object are not lost require.Equal(t, "console", cfg.Encoding) } func TestUnmarshalTracesConfigV020(t *testing.T) { cm, err := confmaptest.LoadConf(filepath.Join("testdata", "v0.2.0_traces.yaml")) require.NoError(t, err) cfg := TracesConfigV030{ Level: configtelemetry.LevelNone, } require.NoError(t, cm.Unmarshal(&cfg)) require.Len(t, cfg.Processors, 3) // check the endpoint is prefixed w/ https require.Equal(t, "https://127.0.0.1:4317", *cfg.Processors[0].Batch.Exporter.OTLP.Endpoint) // check the endpoint is prefixed w/ http require.Equal(t, "http://127.0.0.1:4317", *cfg.Processors[2].Simple.Exporter.OTLP.Endpoint) // ensure defaults set in the original config object are not lost require.Equal(t, configtelemetry.LevelNone, cfg.Level) } func TestUnmarshalMetricsConfigV020(t *testing.T) { cm, err := confmaptest.LoadConf(filepath.Join("testdata", "v0.2.0_metrics.yaml")) require.NoError(t, err) cfg := MetricsConfigV030{ Level: configtelemetry.LevelBasic, } require.NoError(t, cm.Unmarshal(&cfg)) require.Len(t, cfg.Readers, 2) // check the endpoint is prefixed w/ https require.Equal(t, "https://127.0.0.1:4317", *cfg.Readers[0].Periodic.Exporter.OTLP.Endpoint) require.ElementsMatch(t, []config.NameStringValuePair{{Name: "key1", Value: ptr("value1")}, {Name: "key2", Value: ptr("value2")}}, cfg.Readers[0].Periodic.Exporter.OTLP.Headers) // ensure defaults set in the original config object are not lost require.Equal(t, configtelemetry.LevelBasic, cfg.Level) } func ptr[T any](v T) *T { return &v } ================================================ FILE: service/telemetry/otelconftelemetry/internal/migration/v0.3.0.go ================================================ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package migration // import "go.opentelemetry.io/collector/service/telemetry/otelconftelemetry/internal/migration" import ( "time" config "go.opentelemetry.io/contrib/otelconf/v0.3.0" "go.uber.org/zap/zapcore" "go.opentelemetry.io/collector/config/configtelemetry" "go.opentelemetry.io/collector/confmap" ) type TracesConfigV030 struct { // Level configures whether spans are emitted or not, the possible values are: // - "none" indicates that no tracing data should be collected; // - "basic" is the recommended and covers the basics of the service telemetry. Level configtelemetry.Level `mapstructure:"level"` // Propagators is a list of TextMapPropagators from the supported propagators list. Currently, // tracecontext and b3 are supported. By default, the value is set to empty list and // context propagation is disabled. Propagators []string `mapstructure:"propagators"` config.TracerProvider `mapstructure:",squash"` } func (c *TracesConfigV030) Unmarshal(conf *confmap.Conf) error { unmarshaled := *c if err := conf.Unmarshal(c); err != nil { v2TracesConfig := tracesConfigV020{ Level: unmarshaled.Level, Propagators: unmarshaled.Propagators, } // try unmarshaling using v0.2.0 struct if e := conf.Unmarshal(&v2TracesConfig); e != nil { // error could not be resolved through backwards // compatibility attempts return err } // TODO: add a warning log to tell users to migrate their config return tracesConfigV02ToV03(v2TracesConfig, c) } // ensure endpoint normalization occurs for _, r := range c.Processors { if r.Batch != nil { if r.Batch.Exporter.OTLP != nil { r.Batch.Exporter.OTLP.Endpoint = normalizeEndpoint(*r.Batch.Exporter.OTLP.Endpoint, r.Batch.Exporter.OTLP.Insecure) } } if r.Simple != nil { if r.Simple.Exporter.OTLP != nil && r.Simple.Exporter.OTLP.Endpoint != nil { r.Simple.Exporter.OTLP.Endpoint = normalizeEndpoint(*r.Simple.Exporter.OTLP.Endpoint, r.Simple.Exporter.OTLP.Insecure) } } } return nil } type MetricsConfigV030 struct { // Level is the level of telemetry metrics, the possible values are: // - "none" indicates that no telemetry data should be collected; // - "basic" is the recommended and covers the basics of the service telemetry. // - "normal" adds some other indicators on top of basic. // - "detailed" adds dimensions and views to the previous levels. Level configtelemetry.Level `mapstructure:"level"` config.MeterProvider `mapstructure:",squash"` } func (c *MetricsConfigV030) Unmarshal(conf *confmap.Conf) error { unmarshaled := *c if err := conf.Unmarshal(c); err != nil { v02 := metricsConfigV020{ Level: unmarshaled.Level, } // try unmarshaling using v0.2.0 struct if e := conf.Unmarshal(&v02); e != nil { // error could not be resolved through backwards // compatibility attempts return err } // TODO: add a warning log to tell users to migrate their config return metricsConfigV02ToV03(v02, c) } // ensure endpoint normalization occurs for _, r := range c.Readers { if r.Periodic != nil { if r.Periodic.Exporter.OTLP != nil && r.Periodic.Exporter.OTLP.Endpoint != nil { r.Periodic.Exporter.OTLP.Endpoint = normalizeEndpoint(*r.Periodic.Exporter.OTLP.Endpoint, r.Periodic.Exporter.OTLP.Insecure) } } } return nil } type LogsConfigV030 struct { // Level is the minimum enabled logging level. // (default = "INFO") Level zapcore.Level `mapstructure:"level"` // Development puts the logger in development mode, which changes the // behavior of DPanicLevel and takes stacktraces more liberally. // (default = false) Development bool `mapstructure:"development,omitempty"` // Encoding sets the logger's encoding. // Example values are "json", "console". Encoding string `mapstructure:"encoding"` // DisableCaller stops annotating logs with the calling function's file // name and line number. By default, all logs are annotated. // (default = false) DisableCaller bool `mapstructure:"disable_caller,omitempty"` // DisableStacktrace completely disables automatic stacktrace capturing. By // default, stacktraces are captured for WarnLevel and above logs in // development and ErrorLevel and above in production. // (default = false) DisableStacktrace bool `mapstructure:"disable_stacktrace,omitempty"` // Sampling sets a sampling policy. // Default: // sampling: // enabled: true // tick: 10s // initial: 10 // thereafter: 100 // Sampling can be disabled by setting 'enabled' to false Sampling *LogsSamplingConfig `mapstructure:"sampling"` // OutputPaths is a list of URLs or file paths to write logging output to. // The URLs could only be with "file" schema or without schema. // The URLs with "file" schema must be an absolute path. // The URLs without schema are treated as local file paths. // "stdout" and "stderr" are interpreted as os.Stdout and os.Stderr. // see details at Open in zap/writer.go. // (default = ["stderr"]) OutputPaths []string `mapstructure:"output_paths"` // ErrorOutputPaths is a list of URLs or file paths to write zap internal logger errors to. // The URLs could only be with "file" schema or without schema. // The URLs with "file" schema must use absolute paths. // The URLs without schema are treated as local file paths. // "stdout" and "stderr" are interpreted as os.Stdout and os.Stderr. // see details at Open in zap/writer.go. // // Note that this setting only affects the zap internal logger errors. // (default = ["stderr"]) ErrorOutputPaths []string `mapstructure:"error_output_paths"` // InitialFields is a collection of fields to add to the root logger. // Example: // // initial_fields: // foo: "bar" // // By default, there is no initial field. InitialFields map[string]any `mapstructure:"initial_fields,omitempty"` // Processors allow configuration of log record processors to emit logs to // any number of supported backends. Processors []config.LogRecordProcessor `mapstructure:"processors,omitempty"` // DisableZapResource disables adding resource attributes to logs exported through Zap. This does not affect logs exported through OTLP. DisableZapResource bool `mapstructure:"disable_zap_resource,omitempty"` } // LogsSamplingConfig sets a sampling strategy for the logger. Sampling caps the // global CPU and I/O load that logging puts on your process while attempting // to preserve a representative subset of your logs. type LogsSamplingConfig struct { // Enabled enable sampling logging Enabled bool `mapstructure:"enabled"` // Tick represents the interval in seconds that the logger apply each sampling. Tick time.Duration `mapstructure:"tick"` // Initial represents the first M messages logged each Tick. Initial int `mapstructure:"initial"` // Thereafter represents the sampling rate, every Nth message will be sampled after Initial messages are logged during each Tick. // If Thereafter is zero, the logger will drop all the messages after the Initial each Tick. Thereafter int `mapstructure:"thereafter"` } func (c *LogsConfigV030) Unmarshal(conf *confmap.Conf) error { unmarshaled := *c if err := conf.Unmarshal(c); err != nil { v02 := logsConfigV020{ Level: unmarshaled.Level, Development: unmarshaled.Development, Encoding: unmarshaled.Encoding, DisableCaller: unmarshaled.DisableCaller, DisableStacktrace: unmarshaled.DisableStacktrace, Sampling: unmarshaled.Sampling, OutputPaths: unmarshaled.OutputPaths, ErrorOutputPaths: unmarshaled.ErrorOutputPaths, InitialFields: unmarshaled.InitialFields, } // try unmarshaling using v0.2.0 struct if e := conf.Unmarshal(&v02); e != nil { // error could not be resolved through backwards // compatibility attempts return err } // TODO: add a warning log to tell users to migrate their config return logsConfigV02ToV03(v02, c) } // ensure endpoint normalization occurs for _, r := range c.Processors { if r.Batch != nil { if r.Batch.Exporter.OTLP != nil { r.Batch.Exporter.OTLP.Endpoint = normalizeEndpoint(*r.Batch.Exporter.OTLP.Endpoint, r.Batch.Exporter.OTLP.Insecure) } } if r.Simple != nil { if r.Simple.Exporter.OTLP != nil && r.Simple.Exporter.OTLP.Endpoint != nil { r.Simple.Exporter.OTLP.Endpoint = normalizeEndpoint(*r.Simple.Exporter.OTLP.Endpoint, r.Simple.Exporter.OTLP.Insecure) } } } return nil } ================================================ FILE: service/telemetry/otelconftelemetry/internal/migration/v0.3.0_test.go ================================================ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package migration import ( "path/filepath" "testing" "github.com/stretchr/testify/require" "go.opentelemetry.io/collector/confmap/confmaptest" ) func TestUnmarshalLogsConfigV030(t *testing.T) { cm, err := confmaptest.LoadConf(filepath.Join("testdata", "v0.3.0_logs.yaml")) require.NoError(t, err) cfg := LogsConfigV030{} require.NoError(t, cm.Unmarshal(&cfg)) require.Len(t, cfg.Processors, 3) // check the endpoint is prefixed w/ https require.Equal(t, "https://127.0.0.1:4317", *cfg.Processors[0].Batch.Exporter.OTLP.Endpoint) // check the endpoint is prefixed w/ http require.Equal(t, "http://127.0.0.1:4317", *cfg.Processors[2].Simple.Exporter.OTLP.Endpoint) } func TestUnmarshalTracesConfigV030(t *testing.T) { cm, err := confmaptest.LoadConf(filepath.Join("testdata", "v0.3.0_traces.yaml")) require.NoError(t, err) cfg := TracesConfigV030{} require.NoError(t, cm.Unmarshal(&cfg)) require.Len(t, cfg.Processors, 3) // check the endpoint is prefixed w/ https require.Equal(t, "https://127.0.0.1:4317", *cfg.Processors[0].Batch.Exporter.OTLP.Endpoint) // check the endpoint is prefixed w/ http require.Equal(t, "http://127.0.0.1:4317", *cfg.Processors[2].Simple.Exporter.OTLP.Endpoint) } func TestUnmarshalMetricsConfigV030(t *testing.T) { cm, err := confmaptest.LoadConf(filepath.Join("testdata", "v0.3.0_metrics.yaml")) require.NoError(t, err) cfg := MetricsConfigV030{} require.NoError(t, cm.Unmarshal(&cfg)) require.Len(t, cfg.Readers, 2) // check the endpoint is prefixed w/ https require.Equal(t, "https://127.0.0.1:4317", *cfg.Readers[0].Periodic.Exporter.OTLP.Endpoint) } ================================================ FILE: service/telemetry/otelconftelemetry/logger.go ================================================ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconftelemetry // import "go.opentelemetry.io/collector/service/telemetry/otelconftelemetry" import ( "context" otelconf "go.opentelemetry.io/contrib/otelconf/v0.3.0" sdkresource "go.opentelemetry.io/otel/sdk/resource" "go.uber.org/zap" "go.uber.org/zap/zapcore" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/service/telemetry" ) // createLogger creates a Logger and a LoggerProvider from Config. func createLogger( ctx context.Context, set telemetry.LoggerSettings, componentConfig component.Config, ) (*zap.Logger, component.ShutdownFunc, error) { cfg := componentConfig.(*Config) attrs := pcommonAttrsToOTelAttrs(set.Resource) res := sdkresource.NewWithAttributes("", attrs...) // Copied from NewProductionConfig. ec := zap.NewProductionEncoderConfig() ec.EncodeTime = zapcore.ISO8601TimeEncoder zapCfg := zap.Config{ Level: zap.NewAtomicLevelAt(cfg.Logs.Level), Development: cfg.Logs.Development, Encoding: cfg.Logs.Encoding, EncoderConfig: ec, OutputPaths: cfg.Logs.OutputPaths, ErrorOutputPaths: cfg.Logs.ErrorOutputPaths, DisableCaller: cfg.Logs.DisableCaller, DisableStacktrace: cfg.Logs.DisableStacktrace, InitialFields: cfg.Logs.InitialFields, } if zapCfg.Encoding == "console" { // Human-readable timestamps for console format of logs. zapCfg.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder } buildZapLogger := set.BuildZapLogger if buildZapLogger == nil { // For backwards compatibility, use zap.Config.Build // if set.BuildZapLogger is not provided. buildZapLogger = zap.Config.Build } logger, err := buildZapLogger(zapCfg, set.ZapOptions...) if err != nil { return nil, nil, err } // The attributes in res.Attributes(), which are generated in telemetry.go, // are added to logs exported through the LoggerProvider instantiated below. // To make sure they are also exposed in logs written to stdout, we add // them as fields to the Zap core created above using WrapCore. We do NOT // add them to the logger using With, because that would apply to all logs, // even ones exported through the core that wraps the LoggerProvider, // meaning that the attributes would be exported twice. if !cfg.Logs.DisableZapResource && res != nil && len(res.Attributes()) > 0 { logger = logger.WithOptions(zap.WrapCore(func(c zapcore.Core) zapcore.Core { var fields []zap.Field for _, attr := range res.Attributes() { fields = append(fields, zap.String(string(attr.Key), attr.Value.Emit())) } r := zap.Dict("resource", fields...) return c.With([]zapcore.Field{r}) })) } if cfg.Logs.Sampling != nil && cfg.Logs.Sampling.Enabled { logger = logger.WithOptions(zap.WrapCore(func(core zapcore.Core) zapcore.Core { return zapcore.NewSamplerWithOptions( core, cfg.Logs.Sampling.Tick, cfg.Logs.Sampling.Initial, cfg.Logs.Sampling.Thereafter, ) })) } sdk, err := newSDK(ctx, res, otelconf.OpenTelemetryConfiguration{ LoggerProvider: &otelconf.LoggerProvider{ Processors: cfg.Logs.Processors, }, }) if err != nil { return nil, nil, err } // Wrap the zap.Logger with a special zap.Core so scope attributes // can be added and removed dynamically, and tee logs to the // LoggerProvider. loggerProvider := sdk.LoggerProvider() logger = logger.WithOptions(zap.WrapCore(func(core zapcore.Core) zapcore.Core { provider := zapCoreProvider{ sourceCore: core, lp: loggerProvider, scopeName: "go.opentelemetry.io/collector/service", } return provider.newCore() })) return logger, sdk.Shutdown, nil } ================================================ FILE: service/telemetry/otelconftelemetry/logger_tee.go ================================================ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconftelemetry // import "go.opentelemetry.io/collector/service/telemetry/otelconftelemetry" import ( "slices" "go.opentelemetry.io/contrib/bridges/otelzap" "go.opentelemetry.io/otel/log" "go.uber.org/multierr" "go.uber.org/zap" "go.uber.org/zap/zapcore" "go.opentelemetry.io/collector/service/internal/componentattribute" ) type zapCoreProvider struct { sourceCore zapcore.Core lp log.LoggerProvider scopeName string } func (zcp *zapCoreProvider) newCore() zapCore { return zapCore{ sourceCore: zcp.sourceCore, otelCore: otelzap.NewCore( zcp.scopeName, otelzap.WithLoggerProvider(zcp.lp), ), provider: zcp, } } // This struct wraps the original Zap core in order to copy logs to a [log.LoggerProvider] using [otelzap]. // For the copied logs, component attributes are injected as instrumentation scope attributes. // // Note that we intentionally do not use zapcore.NewTee here, because it will simply duplicate all log entries // to each core. The provided Zap core may have sampling or a minimum log level applied to it, so in order to // maintain consistency, we need to ensure that only the logs accepted by the provided core are copied to the // log.LoggerProvider. type zapCore struct { sourceCore zapcore.Core // regular Zap core (logs to stderr) otelCore zapcore.Core // otelzap core (forwards to OTel Logger) provider *zapCoreProvider withFields []zap.Field // additional fields injected by the user using .With } var _ zapcore.Core = zapCore{} func (zc zapCore) With(fields []zapcore.Field) zapcore.Core { zc.sourceCore = zc.sourceCore.With(fields) fields = slices.DeleteFunc(fields, func(field zapcore.Field) bool { scope, ok := componentattribute.ExtractLogScopeAttributes(field) if !ok { return false } // Set scope attributes zc.otelCore = otelzap.NewCore( zc.provider.scopeName, otelzap.WithLoggerProvider(zc.provider.lp), otelzap.WithAttributes(scope...), ).With(zc.withFields) return true }) zc.otelCore = zc.otelCore.With(fields) zc.withFields = append(slices.Clone(zc.withFields), fields...) return zc } func (zc zapCore) Enabled(level zapcore.Level) bool { return zc.sourceCore.Enabled(level) } func (zc zapCore) Check(entry zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry { ce = zc.sourceCore.Check(entry, ce) if ce != nil { // Only log to the otelzap core if the source core accepted the log entry. ce = ce.AddCore(entry, zc.otelCore) } return ce } // This function should never be called, since only the inner cores add themselves to the CheckedEntry. // But like zapcore.multiCore, we still implement it for compatibility with non-conforming users. func (zc zapCore) Write(entry zapcore.Entry, fields []zapcore.Field) error { return multierr.Append( zc.sourceCore.Write(entry, fields), zc.otelCore.Write(entry, fields), ) } func (zc zapCore) Sync() error { return multierr.Append( zc.sourceCore.Sync(), zc.otelCore.Sync(), ) } ================================================ FILE: service/telemetry/otelconftelemetry/logger_test.go ================================================ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconftelemetry // import "go.opentelemetry.io/collector/service/telemetry" import ( "context" "encoding/json" "errors" "io" "net/http" "net/http/httptest" "strings" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" config "go.opentelemetry.io/contrib/otelconf/v0.3.0" "go.opentelemetry.io/otel/attribute" "go.uber.org/zap" "go.uber.org/zap/zapcore" "go.uber.org/zap/zaptest/observer" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/component/componenttest" internalTelemetry "go.opentelemetry.io/collector/internal/telemetry" "go.opentelemetry.io/collector/pdata/plog/plogotlp" "go.opentelemetry.io/collector/service/internal/componentattribute" "go.opentelemetry.io/collector/service/telemetry" ) const ( version = "1.2.3" service = "test-service" testAttribute = "test-attribute" testValue = "test-value" ) func TestCreateLogger(t *testing.T) { tests := []struct { name string wantCoreType any wantErr error cfg Config }{ { name: "no log config", cfg: Config{}, wantErr: errors.New("no encoder name specified"), }, { name: "log config with invalid processors", cfg: Config{ Logs: LogsConfig{ Level: zapcore.DebugLevel, Development: true, Encoding: "console", DisableCaller: true, DisableStacktrace: true, InitialFields: map[string]any{"fieldKey": "filed-value"}, Processors: []config.LogRecordProcessor{ { Batch: &config.BatchLogRecordProcessor{ Exporter: config.LogRecordExporter{ OTLP: &config.OTLP{}, // missing required fields }, }, }, }, }, }, wantErr: errors.New("no valid log exporter"), }, { name: "log config with no processors", cfg: Config{ Logs: LogsConfig{ Level: zapcore.DebugLevel, Development: true, Encoding: "console", DisableCaller: true, DisableStacktrace: true, InitialFields: map[string]any{"fieldKey": "filed-value"}, }, }, }, { name: "log config with processors", cfg: Config{ Logs: LogsConfig{ Level: zapcore.DebugLevel, Development: true, Encoding: "console", DisableCaller: true, DisableStacktrace: true, InitialFields: map[string]any{"fieldKey": "filed-value"}, Processors: []config.LogRecordProcessor{ { Batch: &config.BatchLogRecordProcessor{ Exporter: config.LogRecordExporter{ Console: config.Console{}, }, }, }, }, }, }, }, { name: "log config with sampling", cfg: Config{ Logs: LogsConfig{ Level: zapcore.InfoLevel, Development: false, Encoding: "console", Sampling: &LogsSamplingConfig{ Enabled: true, Tick: 10 * time.Second, Initial: 10, Thereafter: 100, }, OutputPaths: []string{"stderr"}, ErrorOutputPaths: []string{"stderr"}, DisableCaller: false, DisableStacktrace: false, InitialFields: map[string]any(nil), }, }, }, { name: "log config with `disable_resource_attributes` enabled", cfg: Config{ Logs: LogsConfig{ Level: zapcore.InfoLevel, Development: false, Encoding: "console", OutputPaths: []string{"stderr"}, ErrorOutputPaths: []string{"stderr"}, DisableCaller: false, DisableStacktrace: false, InitialFields: map[string]any(nil), DisableZapResource: true, }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { buildInfo := component.BuildInfo{} factory := NewFactory() resource, err := factory.CreateResource(context.Background(), telemetry.Settings{BuildInfo: buildInfo}, &tt.cfg) require.NoError(t, err) _, provider, err := factory.CreateLogger( context.Background(), telemetry.LoggerSettings{ Settings: telemetry.Settings{BuildInfo: buildInfo, Resource: &resource}, BuildZapLogger: zap.Config.Build, }, &tt.cfg, ) if tt.wantErr != nil { require.ErrorContains(t, err, tt.wantErr.Error()) } else { require.NoError(t, err) require.NoError(t, provider.Shutdown(context.Background())) } }) } } func TestCreateLoggerWithResource(t *testing.T) { tests := []struct { name string buildInfo component.BuildInfo resourceConfig map[string]*string wantFields map[string]string setConfig func(cfg *Config) }{ { name: "auto-populated fields only", buildInfo: component.BuildInfo{ Command: "mycommand", Version: "1.0.0", }, resourceConfig: map[string]*string{}, wantFields: map[string]string{ "service.name": "mycommand", "service.version": "1.0.0", "service.instance.id": "", }, }, { name: "override service.name", buildInfo: component.BuildInfo{ Command: "mycommand", Version: "1.0.0", }, resourceConfig: map[string]*string{ "service.name": ptr("custom-service"), }, wantFields: map[string]string{ "service.name": "custom-service", "service.version": "1.0.0", "service.instance.id": "", }, }, { name: "override service.version", buildInfo: component.BuildInfo{ Command: "mycommand", Version: "1.0.0", }, resourceConfig: map[string]*string{ "service.version": ptr("2.0.0"), }, wantFields: map[string]string{ "service.name": "mycommand", "service.version": "2.0.0", "service.instance.id": "", }, }, { name: "custom field with auto-populated", buildInfo: component.BuildInfo{ Command: "mycommand", Version: "1.0.0", }, resourceConfig: map[string]*string{ "custom.field": ptr("custom-value"), }, wantFields: map[string]string{ "service.name": "mycommand", "service.version": "1.0.0", "service.instance.id": "", // Just check presence "custom.field": "custom-value", }, }, { name: "resource with no attributes", buildInfo: component.BuildInfo{}, resourceConfig: nil, wantFields: map[string]string{ // A random UUID is injected for service.instance.id by default "service.instance.id": "", // Just check presence }, }, { name: "validate `DisableResourceAttributes=true` shouldn't add resource fields", buildInfo: component.BuildInfo{ Command: "mycommand", Version: "1.0.0", }, resourceConfig: map[string]*string{}, wantFields: map[string]string{}, setConfig: func(cfg *Config) { cfg.Logs.DisableZapResource = true }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { core, observedLogs := observer.New(zapcore.DebugLevel) cfg := &Config{ Logs: LogsConfig{ Level: zapcore.InfoLevel, Encoding: "json", }, Resource: tt.resourceConfig, } if tt.setConfig != nil { tt.setConfig(cfg) } resource, err := createResource(t.Context(), telemetry.Settings{BuildInfo: tt.buildInfo}, cfg) require.NoError(t, err) set := telemetry.LoggerSettings{ Settings: telemetry.Settings{BuildInfo: tt.buildInfo, Resource: &resource}, BuildZapLogger: func(zap.Config, ...zap.Option) (*zap.Logger, error) { // Redirect logs to the observer core return zap.New(core), nil }, } logger, loggerProvider, err := createLogger(t.Context(), set, cfg) require.NoError(t, err) defer func() { assert.NoError(t, loggerProvider.Shutdown(t.Context())) }() logger.Info("Test log message") require.Len(t, observedLogs.All(), 1) entry := observedLogs.All()[0] // treat empty map as "no expected fields" if len(tt.wantFields) == 0 { assert.Empty(t, entry.Context) return } assert.Equal(t, "resource", entry.Context[0].Key) dict := entry.Context[0].Interface.(zapcore.ObjectMarshaler) enc := zapcore.NewMapObjectEncoder() require.NoError(t, dict.MarshalLogObject(enc)) // Verify all expected fields for k, v := range tt.wantFields { if k == "service.instance.id" { // For service.instance.id just verify it exists since it's auto-generated assert.Contains(t, enc.Fields, k) } else { assert.Equal(t, v, enc.Fields[k]) } } }) } } func TestCreateLoggerZapOptions(t *testing.T) { buildInfo := component.BuildInfo{} factory := NewFactory() cfg := &Config{ Logs: LogsConfig{ Level: zapcore.InfoLevel, Encoding: "json", }, } resource, err := factory.CreateResource( context.Background(), telemetry.Settings{BuildInfo: buildInfo}, cfg, ) require.NoError(t, err) core, observedLogs := observer.New(zapcore.DebugLevel) set := telemetry.LoggerSettings{ Settings: telemetry.Settings{BuildInfo: buildInfo, Resource: &resource}, // Test deprecated behavior: no BuildZapLogger, but ZapOptions provided. BuildZapLogger: nil, ZapOptions: []zap.Option{ zap.WrapCore(func(zapcore.Core) zapcore.Core { return core }), }, } logger, provider, err := factory.CreateLogger(context.Background(), set, cfg) require.NoError(t, err) require.NotNil(t, provider) defer func() { assert.NoError(t, provider.Shutdown(context.Background())) }() testMessage := "Test deprecated zap options" logger.Info(testMessage) require.Len(t, observedLogs.All(), 1) logEntry := observedLogs.All()[0] assert.Equal(t, testMessage, logEntry.Message) assert.Equal(t, zapcore.InfoLevel, logEntry.Level) } func TestLogger_OTLP(t *testing.T) { // Create a backend to receive the logs and assert the content receivedLogs := 0 logger := newOTLPLogger(t, zapcore.InfoLevel, func(req plogotlp.ExportRequest) { logs := req.Logs() rl := logs.ResourceLogs().At(0) resourceAttrs := rl.Resource().Attributes().AsRaw() assert.Equal(t, service, resourceAttrs["service.name"]) assert.Equal(t, version, resourceAttrs["service.version"]) assert.Equal(t, testValue, resourceAttrs[testAttribute]) // Check that the resource attributes are not duplicated in the log records sl := rl.ScopeLogs().At(0) logRecord := sl.LogRecords().At(0) attrs := logRecord.Attributes().AsRaw() assert.NotContains(t, attrs, "service.name") assert.NotContains(t, attrs, "service.version") assert.NotContains(t, attrs, testAttribute) receivedLogs++ }) const totalLogs = 10 for range totalLogs { logger.Info("Test log message") } // Ensure the correct number of logs were received require.Equal(t, totalLogs, receivedLogs) } func newOTLPLogger(t *testing.T, level zapcore.Level, handler func(plogotlp.ExportRequest)) *zap.Logger { srv := createLogsBackend(t, "/v1/logs", handler) t.Cleanup(srv.Close) processors := []config.LogRecordProcessor{{ Simple: &config.SimpleLogRecordProcessor{ Exporter: config.LogRecordExporter{ OTLP: &config.OTLP{ Endpoint: ptr(srv.URL), Protocol: ptr("http/protobuf"), Insecure: ptr(true), }, }, }, }} cfg := &Config{ Logs: LogsConfig{ Level: level, Encoding: "json", Processors: processors, // OutputPaths is empty, so logs are only // written to the OTLP processor }, Resource: map[string]*string{ "service.name": ptr(service), "service.version": ptr(version), testAttribute: ptr(testValue), }, } resource, err := createResource(t.Context(), telemetry.Settings{}, cfg) require.NoError(t, err) logger, shutdown, err := createLogger(t.Context(), telemetry.LoggerSettings{ Settings: telemetry.Settings{Resource: &resource}, BuildZapLogger: zap.Config.Build, }, cfg) require.NoError(t, err) t.Cleanup(func() { assert.NoError(t, shutdown.Shutdown(context.WithoutCancel(t.Context()))) }) return logger } func createLogsBackend(t *testing.T, endpoint string, handler func(plogotlp.ExportRequest)) *httptest.Server { mux := http.NewServeMux() mux.HandleFunc(endpoint, func(_ http.ResponseWriter, request *http.Request) { body, err := io.ReadAll(request.Body) assert.NoError(t, err) defer request.Body.Close() // Unmarshal the protobuf body into logs req := plogotlp.NewExportRequest() err = req.UnmarshalProto(body) assert.NoError(t, err) handler(req) }) return httptest.NewServer(mux) } func TestLogAttributeInjection(t *testing.T) { core, consoleLogs := observer.New(zapcore.DebugLevel) var otlpLogs []plogotlp.ExportRequest srv := createLogsBackend(t, "/v1/logs", func(req plogotlp.ExportRequest) { otlpLogs = append(otlpLogs, req) }) t.Cleanup(srv.Close) cfg := &Config{ Resource: map[string]*string{ "service.instance.id": nil, "service.name": nil, "service.version": nil, }, Logs: LogsConfig{ Encoding: "json", Processors: []config.LogRecordProcessor{{ Simple: &config.SimpleLogRecordProcessor{ Exporter: config.LogRecordExporter{ OTLP: &config.OTLP{ // Send OTLP logs to the mock backend Endpoint: ptr(srv.URL), Protocol: ptr("http/protobuf"), Insecure: ptr(true), }, }, }, }}, }, } resource, err := createResource(t.Context(), telemetry.Settings{}, cfg) require.NoError(t, err) set := telemetry.LoggerSettings{ Settings: telemetry.Settings{Resource: &resource}, BuildZapLogger: func(zap.Config, ...zap.Option) (*zap.Logger, error) { // Redirect console logs to the observer core return zap.New(core), nil }, } sourceLogger, loggerProvider, err := createLogger(t.Context(), set, cfg) require.NoError(t, err) defer func() { assert.NoError(t, loggerProvider.Shutdown(t.Context())) }() ts := componenttest.NewNopTelemetrySettings() ts.Logger = sourceLogger ts = componentattribute.TelemetrySettingsWithAttributes(ts, attribute.NewSet( attribute.String("injected1", "val"), attribute.String("injected2", "val"), )) ts.Logger = ts.Logger.With(zap.String("after", "val")) fields, scope := checkScopes(t, ts.Logger, consoleLogs, &otlpLogs) assert.JSONEq(t, `{"injected1":"val","injected2":"val","after":"val","manual":"val"}`, fields) assert.JSONEq(t, `{"injected1":"val","injected2":"val"}`, scope) ts = internalTelemetry.DropInjectedAttributes(ts, "injected1") fields, scope = checkScopes(t, ts.Logger, consoleLogs, &otlpLogs) assert.JSONEq(t, `{"injected2":"val","after":"val","manual":"val"}`, fields) assert.JSONEq(t, `{"injected2":"val"}`, scope) } func checkScopes(t *testing.T, logger *zap.Logger, consoleLogs *observer.ObservedLogs, otlpLogs *[]plogotlp.ExportRequest) (string, string) { logger.Info("Test log message", zap.String("manual", "val")) require.Len(t, consoleLogs.All(), 1) log := consoleLogs.TakeAll()[0] enc := zapcore.NewJSONEncoder(zapcore.EncoderConfig{}) fieldsBuf, err := enc.EncodeEntry(log.Entry, log.Context) require.NoError(t, err) fieldsStr := strings.TrimSuffix(fieldsBuf.String(), "\n") require.Len(t, *otlpLogs, 1) req := (*otlpLogs)[0] *otlpLogs = nil rls := req.Logs().ResourceLogs() require.Equal(t, 1, rls.Len()) sls := rls.At(0).ScopeLogs() require.Equal(t, 1, sls.Len()) attrs := sls.At(0).Scope().Attributes() scopeBuf, err := json.Marshal(attrs.AsRaw()) require.NoError(t, err) scopeStr := string(scopeBuf) return fieldsStr, scopeStr } ================================================ FILE: service/telemetry/otelconftelemetry/metrics.go ================================================ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconftelemetry // import "go.opentelemetry.io/collector/service/telemetry/otelconftelemetry" import ( "context" config "go.opentelemetry.io/contrib/otelconf/v0.3.0" noopmetric "go.opentelemetry.io/otel/metric/noop" sdkresource "go.opentelemetry.io/otel/sdk/resource" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/config/configtelemetry" "go.opentelemetry.io/collector/service/telemetry" ) func createMeterProvider( ctx context.Context, set telemetry.MeterSettings, componentConfig component.Config, ) (telemetry.MeterProvider, error) { cfg := componentConfig.(*Config) if cfg.Metrics.Level == configtelemetry.LevelNone { set.Logger.Info("Internal metrics telemetry disabled") return noopMeterProvider{MeterProvider: noopmetric.NewMeterProvider()}, nil } else if cfg.Metrics.Views == nil && set.DefaultViews != nil { cfg.Metrics.Views = set.DefaultViews(cfg.Metrics.Level) } attrs := pcommonAttrsToOTelAttrs(set.Resource) res := sdkresource.NewWithAttributes("", attrs...) mpConfig := cfg.Metrics.MeterProvider sdk, err := newSDK(ctx, res, config.OpenTelemetryConfiguration{ MeterProvider: &mpConfig, }) if err != nil { return nil, err } return sdk.MeterProvider().(telemetry.MeterProvider), nil } type noopMeterProvider struct { noopmetric.MeterProvider } func (noopMeterProvider) Shutdown(context.Context) error { return nil } ================================================ FILE: service/telemetry/otelconftelemetry/metrics_test.go ================================================ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconftelemetry import ( "context" "fmt" "io" "net/http" "net/http/httptest" "testing" "time" io_prometheus_client "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" config "go.opentelemetry.io/contrib/otelconf/v0.3.0" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" "go.uber.org/zap" "go.uber.org/zap/zapcore" "go.uber.org/zap/zaptest/observer" "go.opentelemetry.io/collector/config/configtelemetry" "go.opentelemetry.io/collector/pdata/pmetric" "go.opentelemetry.io/collector/pdata/pmetric/pmetricotlp" "go.opentelemetry.io/collector/service/internal/promtest" "go.opentelemetry.io/collector/service/telemetry" ) const ( metricPrefix = "otelcol_" otelPrefix = "otel_sdk_" grpcPrefix = "grpc_" httpPrefix = "http_" counterName = "test_counter" ) var testInstanceID = "test_instance_id" func TestCreateMeterProvider(t *testing.T) { type metricValue struct { value float64 labels map[string]string } for _, tt := range []struct { name string expectedMetrics map[string]metricValue }{ { name: "UseOpenTelemetryForInternalMetrics", expectedMetrics: map[string]metricValue{ metricPrefix + otelPrefix + counterName: { value: 13, labels: map[string]string{}, }, metricPrefix + grpcPrefix + counterName: { value: 11, labels: map[string]string{ "rpc_system": "grpc", }, }, metricPrefix + httpPrefix + counterName: { value: 10, labels: map[string]string{ "http_request_method": "GET", }, }, "target_info": { value: 0, labels: map[string]string{ "service_name": "otelcol", "service_version": "latest", "service_instance_id": testInstanceID, }, }, "promhttp_metric_handler_errors_total": { value: 0, labels: map[string]string{ "cause": "encoding", }, }, }, }, } { prom := promtest.GetAvailableLocalAddressPrometheus(t) endpoint := fmt.Sprintf("http://%s:%d/metrics", *prom.Host, *prom.Port) cfg := createDefaultConfig().(*Config) cfg.Metrics = MetricsConfig{ Level: configtelemetry.LevelDetailed, MeterProvider: config.MeterProvider{ Readers: []config.MetricReader{{ Pull: &config.PullMetricReader{ Exporter: config.PullMetricExporter{Prometheus: prom}, }, }}, }, } cfg.Resource = map[string]*string{ "service.name": ptr("otelcol"), "service.version": ptr("latest"), "service.instance.id": ptr(testInstanceID), } t.Run(tt.name, func(t *testing.T) { resource, err := createResource(t.Context(), telemetry.Settings{}, cfg) require.NoError(t, err) mp, err := createMeterProvider(t.Context(), telemetry.MeterSettings{ Settings: telemetry.Settings{Resource: &resource}, }, cfg) require.NoError(t, err) defer func() { assert.NoError(t, mp.Shutdown(t.Context())) }() createTestMetrics(t, mp) metrics := getMetricsFromPrometheus(t, endpoint) require.Len(t, metrics, len(tt.expectedMetrics)) for metricName, metricValue := range tt.expectedMetrics { mf, present := metrics[metricName] require.True(t, present, "expected metric %q was not present", metricName) if metricName == "promhttp_metric_handler_errors_total" { continue } require.Len(t, mf.Metric, 1, "only one measure should exist for metric %q", metricName) labels := make(map[string]string) for _, pair := range mf.Metric[0].Label { labels[pair.GetName()] = pair.GetValue() } require.Equal(t, metricValue.labels, labels, "labels for metric %q was different than expected", metricName) require.InDelta(t, metricValue.value, mf.Metric[0].Counter.GetValue(), 0.01, "value for metric %q was different than expected", metricName) } }) } } func createTestMetrics(t *testing.T, mp metric.MeterProvider) { // Creates a OTel Go counter counter, err := mp.Meter("collector_test").Int64Counter(metricPrefix+otelPrefix+counterName, metric.WithUnit("ms")) require.NoError(t, err) counter.Add(context.Background(), 13) grpcExampleCounter, err := mp.Meter("go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc").Int64Counter(metricPrefix + grpcPrefix + counterName) require.NoError(t, err) grpcExampleCounter.Add(context.Background(), 11, metric.WithAttributeSet(attribute.NewSet( attribute.String("rpc.system", "grpc"), ))) httpExampleCounter, err := mp.Meter("go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp").Int64Counter(metricPrefix + httpPrefix + counterName) require.NoError(t, err) httpExampleCounter.Add(context.Background(), 10, metric.WithAttributeSet(attribute.NewSet( attribute.String("http.request.method", "GET"), ))) } func getMetricsFromPrometheus(t *testing.T, endpoint string) map[string]*io_prometheus_client.MetricFamily { client := &http.Client{ Timeout: 10 * time.Second, } req, err := http.NewRequest(http.MethodGet, endpoint, http.NoBody) require.NoError(t, err) var rr *http.Response maxRetries := 5 for i := range maxRetries { rr, err = client.Do(req) if err == nil && rr.StatusCode == http.StatusOK { break } // skip sleep on last retry if i < maxRetries-1 { time.Sleep(2 * time.Second) // Wait before retrying } } require.NoError(t, err, "failed to get metrics from Prometheus after %d attempts", maxRetries) require.Equal(t, http.StatusOK, rr.StatusCode, "unexpected status code after %d attempts", maxRetries) defer rr.Body.Close() parser := expfmt.NewTextParser(model.UTF8Validation) parsed, err := parser.TextToMetricFamilies(rr.Body) require.NoError(t, err) return parsed } func TestCreateMeterProvider_Invalid(t *testing.T) { cfg := createDefaultConfig().(*Config) cfg.Logs.Level = zapcore.FatalLevel cfg.Traces.Level = configtelemetry.LevelNone cfg.Metrics.Readers = []config.MetricReader{{ // Invalid -- no OTLP protocol defined Periodic: &config.PeriodicMetricReader{Exporter: config.PushMetricExporter{OTLP: &config.OTLPMetric{}}}, }} resource, err := createResource(t.Context(), telemetry.Settings{}, cfg) require.NoError(t, err) _, err = createMeterProvider(t.Context(), telemetry.MeterSettings{ Settings: telemetry.Settings{Resource: &resource}, }, cfg) require.EqualError(t, err, "no valid metric exporter") } func TestCreateMeterProvider_Disabled(t *testing.T) { cfg := createDefaultConfig().(*Config) cfg.Metrics.Readers = []config.MetricReader{{ // Invalid -- no OTLP protocol defined Periodic: &config.PeriodicMetricReader{Exporter: config.PushMetricExporter{OTLP: &config.OTLPMetric{}}}, }} core, observedLogs := observer.New(zapcore.DebugLevel) factory := NewFactory() resource, err := factory.CreateResource(context.Background(), telemetry.Settings{}, cfg) require.NoError(t, err) settings := telemetry.MeterSettings{ Settings: telemetry.Settings{Resource: &resource}, } settings.Logger = zap.New(core) _, err = factory.CreateMeterProvider(context.Background(), settings, cfg) require.EqualError(t, err, "no valid metric exporter") assert.Zero(t, observedLogs.Len()) // Setting Metrics.Level to LevelNone disables metrics, // so the invalid configuration should not cause an error. cfg.Metrics.Level = configtelemetry.LevelNone resource2, err := createResource(t.Context(), telemetry.Settings{}, cfg) require.NoError(t, err) settings.Resource = &resource2 mp, err := createMeterProvider(t.Context(), settings, cfg) require.NoError(t, err) assert.NoError(t, mp.Shutdown(t.Context())) require.Equal(t, 1, observedLogs.Len()) assert.Equal(t, "Internal metrics telemetry disabled", observedLogs.All()[0].Message) } // Test that the MeterProvider implements the 'Enabled' functionality. // See https://pkg.go.dev/go.opentelemetry.io/otel/sdk/metric/internal/x#readme-instrument-enabled. func TestInstrumentEnabled(t *testing.T) { prom := promtest.GetAvailableLocalAddressPrometheus(t) cfg := createDefaultConfig().(*Config) cfg.Metrics.Readers = []config.MetricReader{{ Pull: &config.PullMetricReader{Exporter: config.PullMetricExporter{Prometheus: prom}}, }} resource, err := createResource(t.Context(), telemetry.Settings{}, cfg) require.NoError(t, err) meterProvider, err := createMeterProvider(t.Context(), telemetry.MeterSettings{ Settings: telemetry.Settings{Resource: &resource}, }, cfg) require.NoError(t, err) defer func() { assert.NoError(t, meterProvider.Shutdown(t.Context())) }() meter := meterProvider.Meter("go.opentelemetry.io/collector/service/telemetry") type enabledInstrument interface{ Enabled(context.Context) bool } intCnt, err := meter.Int64Counter("int64.counter") require.NoError(t, err) assert.Implements(t, new(enabledInstrument), intCnt) intUpDownCnt, err := meter.Int64UpDownCounter("int64.updowncounter") require.NoError(t, err) assert.Implements(t, new(enabledInstrument), intUpDownCnt) intHist, err := meter.Int64Histogram("int64.updowncounter") require.NoError(t, err) assert.Implements(t, new(enabledInstrument), intHist) intGauge, err := meter.Int64Gauge("int64.updowncounter") require.NoError(t, err) assert.Implements(t, new(enabledInstrument), intGauge) floatCnt, err := meter.Float64Counter("int64.updowncounter") require.NoError(t, err) assert.Implements(t, new(enabledInstrument), floatCnt) floatUpDownCnt, err := meter.Float64UpDownCounter("int64.updowncounter") require.NoError(t, err) assert.Implements(t, new(enabledInstrument), floatUpDownCnt) floatHist, err := meter.Float64Histogram("int64.updowncounter") require.NoError(t, err) assert.Implements(t, new(enabledInstrument), floatHist) floatGauge, err := meter.Float64Gauge("int64.updowncounter") require.NoError(t, err) assert.Implements(t, new(enabledInstrument), floatGauge) } func TestTelemetryMetrics_DefaultViews(t *testing.T) { type testcase struct { configuredViews []config.View expectedMeterNames []string } defaultViews := func(level configtelemetry.Level) []config.View { assert.Equal(t, configtelemetry.LevelDetailed, level) return []config.View{{ Selector: &config.ViewSelector{ MeterName: ptr("a"), }, Stream: &config.ViewStream{ Aggregation: &config.ViewStreamAggregation{ Drop: config.ViewStreamAggregationDrop{}, }, }, }} } for name, tc := range map[string]testcase{ "no_configured_views": { expectedMeterNames: []string{"b", "c"}, }, "configured_views": { configuredViews: []config.View{{ Selector: &config.ViewSelector{ MeterName: ptr("b"), }, Stream: &config.ViewStream{ Aggregation: &config.ViewStreamAggregation{ Drop: config.ViewStreamAggregationDrop{}, }, }, }}, expectedMeterNames: []string{"a", "c"}, }, } { t.Run(name, func(t *testing.T) { var metrics pmetric.Metrics srv := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, req *http.Request) { body, err := io.ReadAll(req.Body) assert.NoError(t, err) exportRequest := pmetricotlp.NewExportRequest() assert.NoError(t, exportRequest.UnmarshalProto(body)) metrics = exportRequest.Metrics() })) defer srv.Close() cfg := createDefaultConfig().(*Config) cfg.Metrics.Level = configtelemetry.LevelDetailed cfg.Metrics.Views = tc.configuredViews cfg.Metrics.Readers = []config.MetricReader{{ Periodic: &config.PeriodicMetricReader{ Exporter: config.PushMetricExporter{ OTLP: &config.OTLPMetric{ Endpoint: ptr(srv.URL), Protocol: ptr("http/protobuf"), Insecure: ptr(true), }, }, }, }} factory := NewFactory() settings := telemetry.MeterSettings{DefaultViews: defaultViews} provider, err := factory.CreateMeterProvider(t.Context(), settings, cfg) require.NoError(t, err) for _, meterName := range []string{"a", "b", "c"} { counter, _ := provider.Meter(meterName).Int64Counter(meterName + ".counter") counter.Add(t.Context(), 1) } require.NoError(t, provider.Shutdown(t.Context())) // should flush metrics var scopes []string for _, rm := range metrics.ResourceMetrics().All() { for _, sm := range rm.ScopeMetrics().All() { scopes = append(scopes, sm.Scope().Name()) } } assert.ElementsMatch(t, tc.expectedMeterNames, scopes) }) } } ================================================ FILE: service/telemetry/otelconftelemetry/package_test.go ================================================ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconftelemetry import ( "testing" "go.uber.org/goleak" ) func TestMain(m *testing.M) { goleak.VerifyTestMain(m) } ================================================ FILE: service/telemetry/otelconftelemetry/resource.go ================================================ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconftelemetry // import "go.opentelemetry.io/collector/service/telemetry/otelconftelemetry" import ( "context" "fmt" "go.opentelemetry.io/otel/attribute" sdkresource "go.opentelemetry.io/otel/sdk/resource" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/pdata/pcommon" "go.opentelemetry.io/collector/service/internal/resource" "go.opentelemetry.io/collector/service/telemetry" ) func createResource( _ context.Context, set telemetry.Settings, componentConfig component.Config, ) (pcommon.Resource, error) { res := newResource(set, componentConfig.(*Config)) pcommonRes := pcommon.NewResource() for _, keyValue := range res.Attributes() { key := string(keyValue.Key) pcommonRes.Attributes().PutStr(key, mustAttributeValueString(key, keyValue.Value)) } return pcommonRes, nil } func newResource(set telemetry.Settings, cfg *Config) *sdkresource.Resource { return resource.New(set.BuildInfo, cfg.Resource) } func mustAttributeValueString(k string, v attribute.Value) string { if v.Type() != attribute.STRING { // We only support string-type resource attributes in the configuration. panic(fmt.Errorf("attribute %q: expected string, got %s", k, v.Type())) } return v.AsString() } // pcommonAttrsToOTelAttrs gets the Resource attributes to OpenTelemetry attribute.KeyValue slice. func pcommonAttrsToOTelAttrs(resource *pcommon.Resource) []attribute.KeyValue { var result []attribute.KeyValue if resource != nil { attrs := resource.Attributes() attrs.Range(func(k string, v pcommon.Value) bool { result = append(result, attribute.String(k, v.AsString())) return true }) } return result } ================================================ FILE: service/telemetry/otelconftelemetry/resource_test.go ================================================ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconftelemetry // import "go.opentelemetry.io/collector/service/telemetry" import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/service/telemetry" ) func TestCreateResource(t *testing.T) { t.Run("default", func(t *testing.T) { cfg := createDefaultConfig().(*Config) set := telemetry.Settings{BuildInfo: component.BuildInfo{Command: "otelcol", Version: "latest"}} res, err := createResource(t.Context(), set, cfg) require.NoError(t, err) raw := res.Attributes().AsRaw() assert.Contains(t, raw, "service.instance.id") delete(raw, "service.instance.id") // remove since it's random assert.Equal(t, map[string]any{ "service.name": "otelcol", "service.version": "latest", }, raw) }) t.Run("with resource attributes", func(t *testing.T) { cfg := createDefaultConfig().(*Config) cfg.Resource = map[string]*string{ "extra.attr": ptr("value"), "service.name": ptr("custom-service"), "service.version": ptr("0.1.0"), "service.instance.id": nil, // removes the attribute } set := telemetry.Settings{BuildInfo: component.BuildInfo{Command: "otelcol", Version: "latest"}} res, err := createResource(t.Context(), set, cfg) require.NoError(t, err) raw := res.Attributes().AsRaw() assert.Equal(t, map[string]any{ "extra.attr": "value", "service.name": "custom-service", "service.version": "0.1.0", }, raw) }) } ================================================ FILE: service/telemetry/otelconftelemetry/sdk.go ================================================ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconftelemetry // import "go.opentelemetry.io/collector/service/telemetry/otelconftelemetry" import ( "context" config "go.opentelemetry.io/contrib/otelconf/v0.3.0" sdkresource "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.38.0" ) func newSDK(ctx context.Context, res *sdkresource.Resource, conf config.OpenTelemetryConfiguration) (config.SDK, error) { resourceAttrs := make([]config.AttributeNameValue, 0, res.Len()) for _, r := range res.Attributes() { key := string(r.Key) resourceAttrs = append(resourceAttrs, config.AttributeNameValue{ Name: key, Value: mustAttributeValueString(key, r.Value), }) } conf.Resource = &config.Resource{ SchemaUrl: ptr(semconv.SchemaURL), Attributes: resourceAttrs, } return config.NewSDK(config.WithContext(ctx), config.WithOpenTelemetryConfiguration(conf)) } ================================================ FILE: service/telemetry/otelconftelemetry/testdata/config_deprecated_address.yaml ================================================ logs: level: "info" metrics: level: "basic" address: "localhost:6666" ================================================ FILE: service/telemetry/otelconftelemetry/testdata/config_deprecated_address_and_readers.yaml ================================================ logs: level: "info" metrics: level: "basic" address: "192.168.0.1:6666" readers: - pull: exporter: prometheus: host: "localhost" port: 9999 ================================================ FILE: service/telemetry/otelconftelemetry/testdata/config_empty.yaml ================================================ ================================================ FILE: service/telemetry/otelconftelemetry/testdata/config_empty_readers.yaml ================================================ metrics: level: "basic" readers: [] ================================================ FILE: service/telemetry/otelconftelemetry/testdata/config_invalid_deprecated_address.yaml ================================================ logs: level: "info" metrics: level: "basic" address: "1212:212121:2121" ================================================ FILE: service/telemetry/otelconftelemetry/testdata/config_invalid_metrics_empty_readers.yaml ================================================ metrics: level: "basic" readers: [] ================================================ FILE: service/telemetry/otelconftelemetry/testdata/config_invalid_metrics_views_feature_gate.yaml ================================================ metrics: level: detailed views: - selector: {} stream: {} ================================================ FILE: service/telemetry/otelconftelemetry/testdata/config_invalid_metrics_views_level.yaml ================================================ metrics: level: basic views: - selector: {} stream: {} ================================================ FILE: service/telemetry/otelconftelemetry/testdata/config_invalid_unknown_field.yaml ================================================ metrics: unknown: basic views: - selector: {} stream: {} ================================================ FILE: service/telemetry/otelconftelemetry/testdata/config_logs.yaml ================================================ logs: level: info development: true disable_caller: true disable_stacktrace: true encoding: console output_paths: [stderr] error_output_paths: [stderr] initial_fields: fieldKey: fieldValue sampling: enabled: false tick: 1s initial: 234 thereafter: 567 processors: - batch: exporter: console: {} ================================================ FILE: service/telemetry/otelconftelemetry/testdata/config_metrics_empty_readers.yaml ================================================ metrics: level: "none" readers: [] ================================================ FILE: service/telemetry/otelconftelemetry/tracer.go ================================================ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconftelemetry // import "go.opentelemetry.io/collector/service/telemetry/otelconftelemetry" import ( "context" "errors" "fmt" config "go.opentelemetry.io/contrib/otelconf/v0.3.0" "go.opentelemetry.io/contrib/propagators/b3" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/propagation" sdkresource "go.opentelemetry.io/otel/sdk/resource" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace/embedded" "go.opentelemetry.io/otel/trace/noop" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/config/configtelemetry" "go.opentelemetry.io/collector/service/telemetry" ) const ( // supported trace propagators traceContextPropagator = "tracecontext" b3Propagator = "b3" ) func createTracerProvider( ctx context.Context, set telemetry.TracerSettings, componentConfig component.Config, ) (telemetry.TracerProvider, error) { cfg := componentConfig.(*Config) if cfg.Traces.Level == configtelemetry.LevelNone { set.Logger.Info("Internal trace telemetry disabled") return &noopNoContextTracerProvider{}, nil } propagator, err := textMapPropagatorFromConfig(cfg.Traces.Propagators) if err != nil { return nil, fmt.Errorf("error creating propagator: %w", err) } otel.SetTextMapPropagator(propagator) attrs := pcommonAttrsToOTelAttrs(set.Resource) res := sdkresource.NewWithAttributes("", attrs...) sdk, err := newSDK(ctx, res, config.OpenTelemetryConfiguration{ TracerProvider: &cfg.Traces.TracerProvider, }) if err != nil { return nil, err } return sdk.TracerProvider().(telemetry.TracerProvider), nil } var errUnsupportedPropagator = errors.New("unsupported trace propagator") type noopNoContextTracer struct { embedded.Tracer } var noopSpan = noop.Span{} func (n *noopNoContextTracer) Start(ctx context.Context, _ string, _ ...trace.SpanStartOption) (context.Context, trace.Span) { return ctx, noopSpan } type noopNoContextTracerProvider struct { embedded.TracerProvider } func (n *noopNoContextTracerProvider) Shutdown(_ context.Context) error { return nil } func (n *noopNoContextTracerProvider) Tracer(_ string, _ ...trace.TracerOption) trace.Tracer { return &noopNoContextTracer{} } func textMapPropagatorFromConfig(props []string) (propagation.TextMapPropagator, error) { var textMapPropagators []propagation.TextMapPropagator for _, prop := range props { switch prop { case traceContextPropagator: textMapPropagators = append(textMapPropagators, propagation.TraceContext{}) case b3Propagator: textMapPropagators = append(textMapPropagators, b3.New()) default: return nil, errUnsupportedPropagator } } return propagation.NewCompositeTextMapPropagator(textMapPropagators...), nil } ================================================ FILE: service/telemetry/otelconftelemetry/tracer_test.go ================================================ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconftelemetry // import "go.opentelemetry.io/collector/service/telemetry" import ( "context" "io" "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" config "go.opentelemetry.io/contrib/otelconf/v0.3.0" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/propagation" "go.uber.org/zap" "go.uber.org/zap/zapcore" "go.uber.org/zap/zaptest/observer" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/config/configtelemetry" "go.opentelemetry.io/collector/pdata/ptrace" "go.opentelemetry.io/collector/pdata/ptrace/ptraceotlp" "go.opentelemetry.io/collector/service/telemetry" ) func TestCreateTracerProvider(t *testing.T) { var received []ptrace.Traces mux := http.NewServeMux() mux.HandleFunc("/v1/traces", func(_ http.ResponseWriter, req *http.Request) { body, err := io.ReadAll(req.Body) assert.NoError(t, err) exportRequest := ptraceotlp.NewExportRequest() assert.NoError(t, exportRequest.UnmarshalProto(body)) received = append(received, exportRequest.Traces()) }) srv := httptest.NewServer(mux) defer srv.Close() cfg := createDefaultConfig().(*Config) cfg.Traces.Propagators = []string{"b3", "tracecontext"} cfg.Traces.Processors = []config.SpanProcessor{newOTLPSimpleSpanProcessor(srv)} resource, err := createResource(t.Context(), telemetry.Settings{ BuildInfo: component.BuildInfo{Command: "otelcol", Version: "latest"}, }, cfg) require.NoError(t, err) provider, err := createTracerProvider(t.Context(), telemetry.TracerSettings{ Settings: telemetry.Settings{ BuildInfo: component.BuildInfo{Command: "otelcol", Version: "latest"}, Resource: &resource, }, }, cfg) require.NoError(t, err) defer func() { assert.NoError(t, provider.Shutdown(t.Context())) }() tracer := provider.Tracer("test_tracer") _, span := tracer.Start(context.Background(), "test_span") span.End() require.Len(t, received, 1) traces := received[0] require.Equal(t, 1, traces.SpanCount()) assert.Equal(t, "test_span", traces.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0).Name()) } func TestCreateTracerProvider_Invalid(t *testing.T) { cfg := createDefaultConfig().(*Config) cfg.Logs.Level = zapcore.FatalLevel cfg.Metrics.Level = configtelemetry.LevelNone cfg.Traces.Processors = []config.SpanProcessor{{ Simple: &config.SimpleSpanProcessor{ Exporter: config.SpanExporter{ OTLP: &config.OTLP{ /* missing endpoint, etc. */ }, }, }, }} resource, err := createResource(t.Context(), telemetry.Settings{}, cfg) require.NoError(t, err) _, err = createTracerProvider(t.Context(), telemetry.TracerSettings{ Settings: telemetry.Settings{Resource: &resource}, }, cfg) require.EqualError(t, err, "no valid span exporter") } func TestCreateTracerProvider_Propagators(t *testing.T) { mux := http.NewServeMux() mux.HandleFunc("/v1/traces", func(http.ResponseWriter, *http.Request) {}) srv := httptest.NewServer(mux) defer srv.Close() cfg := createDefaultConfig().(*Config) cfg.Traces.Propagators = []string{"b3", "tracecontext"} cfg.Traces.Processors = []config.SpanProcessor{newOTLPSimpleSpanProcessor(srv)} resource, err := createResource(t.Context(), telemetry.Settings{ BuildInfo: component.BuildInfo{Command: "otelcol", Version: "latest"}, }, cfg) require.NoError(t, err) provider, err := createTracerProvider(t.Context(), telemetry.TracerSettings{ Settings: telemetry.Settings{ BuildInfo: component.BuildInfo{Command: "otelcol", Version: "latest"}, Resource: &resource, }, }, cfg) require.NoError(t, err) defer func() { assert.NoError(t, provider.Shutdown(t.Context())) }() propagator := otel.GetTextMapPropagator() require.NotNil(t, propagator) tracer := provider.Tracer("test_tracer") ctx, span := tracer.Start(context.Background(), "test_span") mapCarrier := make(propagation.MapCarrier) propagator.Inject(ctx, mapCarrier) span.End() assert.Contains(t, mapCarrier, "b3") assert.Contains(t, mapCarrier, "traceparent") } func TestCreateTracerProvider_InvalidPropagator(t *testing.T) { cfg := createDefaultConfig().(*Config) cfg.Traces.Propagators = []string{"invalid"} resource, err := createResource(t.Context(), telemetry.Settings{}, cfg) require.NoError(t, err) _, err = createTracerProvider(t.Context(), telemetry.TracerSettings{ Settings: telemetry.Settings{Resource: &resource}, }, cfg) assert.EqualError(t, err, "error creating propagator: unsupported trace propagator") } func TestCreateTracerProvider_Disabled(t *testing.T) { var received int mux := http.NewServeMux() mux.HandleFunc("/v1/traces", func(http.ResponseWriter, *http.Request) { received++ }) srv := httptest.NewServer(mux) defer srv.Close() cfg := createDefaultConfig().(*Config) cfg.Traces.Level = configtelemetry.LevelNone cfg.Traces.Processors = []config.SpanProcessor{newOTLPSimpleSpanProcessor(srv)} core, observedLogs := observer.New(zapcore.DebugLevel) resource, err := createResource(t.Context(), telemetry.Settings{ BuildInfo: component.BuildInfo{Command: "otelcol", Version: "latest"}, }, cfg) require.NoError(t, err) settings := telemetry.TracerSettings{ Settings: telemetry.Settings{ BuildInfo: component.BuildInfo{Command: "otelcol", Version: "latest"}, Resource: &resource, }, Logger: zap.New(core), } provider, err := createTracerProvider(t.Context(), settings, cfg) require.NoError(t, err) defer func() { assert.NoError(t, provider.Shutdown(t.Context())) }() require.Equal(t, 1, observedLogs.Len()) assert.Equal(t, "Internal trace telemetry disabled", observedLogs.All()[0].Message) tracer := provider.Tracer("test_tracer") _, span := tracer.Start(context.Background(), "test_span") span.End() assert.Equal(t, 0, received) } func newOTLPSimpleSpanProcessor(srv *httptest.Server) config.SpanProcessor { return config.SpanProcessor{ Simple: &config.SimpleSpanProcessor{ Exporter: config.SpanExporter{ OTLP: &config.OTLP{ Endpoint: ptr(srv.URL), Protocol: ptr("http/protobuf"), Insecure: ptr(true), }, }, }, } } ================================================ FILE: service/telemetry/telemetry.go ================================================ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package telemetry // import "go.opentelemetry.io/collector/service/telemetry" import ( "context" otelconf "go.opentelemetry.io/contrib/otelconf/v0.3.0" "go.opentelemetry.io/otel/metric" noopmetric "go.opentelemetry.io/otel/metric/noop" "go.opentelemetry.io/otel/trace" nooptrace "go.opentelemetry.io/otel/trace/noop" "go.uber.org/zap" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/config/configtelemetry" "go.opentelemetry.io/collector/pdata/pcommon" ) // LoggerSettings holds settings for building logger providers. type LoggerSettings struct { Settings // ZapOptions contains options for creating the zap logger. // // Deprecated [v0.142.0]: use BuildZapLogger instead. // This field will be removed in the future, and options // must be injected through BuildZapLogger. ZapOptions []zap.Option // BuildZapLogger holds a function for building a *zap.Logger // from a zap.Config and options. // // If BuildZapLogger is nil, zap.Config.Build should be used. // NOTE: in the future this field will be required. BuildZapLogger func(zap.Config, ...zap.Option) (*zap.Logger, error) } // MeterSettings holds settings for building meter providers. type MeterSettings struct { Settings // Logger is a zap.Logger that may be used for logging details // of the MeterProvider's construction, and by the MeterProvider // for logging its internal operations. Logger *zap.Logger // DefaultViews holds a function that returns default metric // views for the given internal telemetry metrics level. // // The meter provider is expected to use this if no user-provided // view configuration is supplied. // // TODO we should not use otelconf.View directly here, change // to something independent of otelconf. DefaultViews func(configtelemetry.Level) []otelconf.View } // TracerSettings holds settings for building tracer providers. type TracerSettings struct { Settings // Logger is a zap.Logger that may be used for logging details // of the TracerProvider's construction, and by the TracerProvider // for logging its internal operations. Logger *zap.Logger } // Settings holds common settings for building telemetry providers. type Settings struct { // BuildInfo contains build information about the collector. BuildInfo component.BuildInfo // Resource is the telemetry resource that should be used by all telemetry providers. Resource *pcommon.Resource } // Factory is a factory interface for internal telemetry. // // This interface cannot be directly implemented. Implementations must // use the NewFactory to implement it. // // NOTE This API is experimental and will change soon - use at your own risk. // See https://github.com/open-telemetry/opentelemetry-collector/issues/4970 type Factory interface { // CreateDefaultConfig creates the default configuration for the telemetry. CreateDefaultConfig() component.Config // CreateResource creates a pcommon.Resource representing the collector. // This may be used by components in their internal telemetry. CreateResource(context.Context, Settings, component.Config) (pcommon.Resource, error) // CreateLogger creates a zap.Logger that may be used by components to // log their internal operations, along with a function that must be called // when the service is shutting down. CreateLogger(context.Context, LoggerSettings, component.Config) (*zap.Logger, component.ShutdownFunc, error) // CreateMeterProvider creates a metric.MeterProvider that may be used // by components to record metrics relating to their internal operations. CreateMeterProvider(context.Context, MeterSettings, component.Config) (MeterProvider, error) // CreateTracerProvider creates a trace.TracerProvider that may be used // by components to trace their internal operations. // // If the returned provider is a wrapper, consider implementing // the `Unwrap() trace.TracerProvider` method to grant components access to the underlying SDK. CreateTracerProvider(context.Context, TracerSettings, component.Config) (TracerProvider, error) // unexportedFactoryFunc is used to prevent external implementations of Factory. unexportedFactoryFunc() } // MeterProvider is a metric.MeterProvider that can be shutdown. type MeterProvider interface { metric.MeterProvider Shutdown(context.Context) error } // TracerProvider is a trace.TracerProvider that can be shutdown. type TracerProvider interface { trace.TracerProvider Shutdown(context.Context) error } type FactoryOption interface { applyOption(*factory) } // factoryOptionFunc is an FactoryOption created through a function. type factoryOptionFunc func(*factory) func (f factoryOptionFunc) applyOption(o *factory) { f(o) } type factory struct { component.CreateDefaultConfigFunc createResourceFunc CreateResourceFunc createLoggerFunc CreateLoggerFunc createMeterProviderFunc CreateMeterProviderFunc createTracerProviderFunc CreateTracerProviderFunc } // NewFactory returns a Factory. func NewFactory(createDefaultConfig component.CreateDefaultConfigFunc, opts ...FactoryOption) Factory { f := &factory{ CreateDefaultConfigFunc: createDefaultConfig, } for _, opt := range opts { opt.applyOption(f) } return f } // WithCreateResource overrides the default CreateResource implementation, // which creates an empty resource. func WithCreateResource(createResource CreateResourceFunc) FactoryOption { return factoryOptionFunc(func(f *factory) { f.createResourceFunc = createResource }) } // CreateResourceFunc is the equivalent of Factory.CreateResource. type CreateResourceFunc func(context.Context, Settings, component.Config) (pcommon.Resource, error) // WithCreateLogger overrides the default CreateLogger implementation, // which creates a noop logger and noop logger provider. func WithCreateLogger(createLogger CreateLoggerFunc) FactoryOption { return factoryOptionFunc(func(f *factory) { f.createLoggerFunc = createLogger }) } // WithCreateLogger is the equivalent of Factory.CreateLogger. type CreateLoggerFunc func(context.Context, LoggerSettings, component.Config) (*zap.Logger, component.ShutdownFunc, error) // WithCreateMeterProvider overrides the default CreateMeterProvider func WithCreateMeterProvider(createMeterProvider CreateMeterProviderFunc) FactoryOption { return factoryOptionFunc(func(f *factory) { f.createMeterProviderFunc = createMeterProvider }) } // CreateMeterProviderFunc is the equivalent of Factory.CreateMeterProvider. type CreateMeterProviderFunc func(context.Context, MeterSettings, component.Config) (MeterProvider, error) // WithCreateTracerProvider overrides the default CreateTracerProvider // implementation, which creates a noop tracer provider. func WithCreateTracerProvider(createTracerProvider CreateTracerProviderFunc) FactoryOption { return factoryOptionFunc(func(f *factory) { f.createTracerProviderFunc = createTracerProvider }) } // CreateTracerProviderFunc is the equivalent of Factory.CreateTracerProvider. type CreateTracerProviderFunc func(context.Context, TracerSettings, component.Config) (TracerProvider, error) func (*factory) unexportedFactoryFunc() {} func (f *factory) CreateResource(ctx context.Context, settings Settings, cfg component.Config) (pcommon.Resource, error) { if f.createResourceFunc == nil { return pcommon.NewResource(), nil } return f.createResourceFunc(ctx, settings, cfg) } func (f *factory) CreateLogger(ctx context.Context, settings LoggerSettings, cfg component.Config) (*zap.Logger, component.ShutdownFunc, error) { if f.createLoggerFunc == nil { logger := zap.NewNop() nopShutdown := component.ShutdownFunc(nil) return logger, nopShutdown, nil } return f.createLoggerFunc(ctx, settings, cfg) } func (f *factory) CreateMeterProvider(ctx context.Context, settings MeterSettings, cfg component.Config) (MeterProvider, error) { if f.createMeterProviderFunc == nil { return noopMeterProvider{MeterProvider: noopmetric.NewMeterProvider()}, nil } return f.createMeterProviderFunc(ctx, settings, cfg) } func (f *factory) CreateTracerProvider(ctx context.Context, settings TracerSettings, cfg component.Config) (TracerProvider, error) { if f.createTracerProviderFunc == nil { return noopTracerProvider{TracerProvider: nooptrace.NewTracerProvider()}, nil } return f.createTracerProviderFunc(ctx, settings, cfg) } type noopMeterProvider struct { noopmetric.MeterProvider component.ShutdownFunc } type noopTracerProvider struct { nooptrace.TracerProvider component.ShutdownFunc } ================================================ FILE: service/telemetry/telemetry_test.go ================================================ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package telemetry // import "go.opentelemetry.io/collector/service/telemetry" import ( "context" "errors" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" 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" ) func TestNewFactory_CreateDefaultConfig(t *testing.T) { var config component.Config = new(struct{}) factory := NewFactory(func() component.Config { return config }) require.NotNil(t, factory) assert.Equal(t, config, factory.CreateDefaultConfig()) } func TestNewFactory_Defaults(t *testing.T) { factory := NewFactory(nil) require.NotNil(t, factory) res, err := factory.CreateResource(context.Background(), Settings{}, nil) require.NoError(t, err) assert.Equal(t, pcommon.NewResource(), res) logger, loggerShutdownFunc, err := factory.CreateLogger(context.Background(), LoggerSettings{}, nil) require.NoError(t, err) assert.Equal(t, zap.NewNop(), logger) assert.Nil(t, loggerShutdownFunc) meterProvider, err := factory.CreateMeterProvider(context.Background(), MeterSettings{}, nil) require.NoError(t, err) assert.Equal(t, noopMeterProvider{MeterProvider: noopmetric.NewMeterProvider()}, meterProvider) tracerProvider, err := factory.CreateTracerProvider(context.Background(), TracerSettings{}, nil) require.NoError(t, err) assert.Equal(t, noopTracerProvider{TracerProvider: nooptrace.NewTracerProvider()}, tracerProvider) } func TestNewFactory_Options(t *testing.T) { var called []string factory := NewFactory(nil, factoryOptionFunc(func(*factory) { called = append(called, "option1") }), factoryOptionFunc(func(*factory) { called = append(called, "option2") })) require.NotNil(t, factory) assert.Equal(t, []string{"option1", "option2"}, called) } func TestNewFactory_CreateResource(t *testing.T) { type contextKey struct{} var config component.Config = new(struct{}) settings := Settings{ BuildInfo: component.NewDefaultBuildInfo(), } ctx := context.WithValue(context.Background(), contextKey{}, 123) dummyResource := pcommon.NewResource() dummyResource.Attributes().PutStr("what", "ever") factory := NewFactory(nil, WithCreateResource( func(ctx context.Context, set Settings, cfg component.Config) (pcommon.Resource, error) { assert.Equal(t, 123, ctx.Value(contextKey{})) assert.Equal(t, settings, set) assert.Equal(t, config, cfg) return dummyResource, errors.New("not implemented") }, )) require.NotNil(t, factory) resource, err := factory.CreateResource(ctx, settings, config) require.EqualError(t, err, "not implemented") assert.Equal(t, dummyResource, resource) } func TestNewFactory_CreateLogger(t *testing.T) { type contextKey struct{} var config component.Config = new(struct{}) settings := LoggerSettings{ Settings: Settings{BuildInfo: component.NewDefaultBuildInfo()}, } ctx := context.WithValue(context.Background(), contextKey{}, 123) shutdownCalled := false dummyLogger := new(zap.Logger) factory := NewFactory(nil, WithCreateLogger( func(ctx context.Context, set LoggerSettings, cfg component.Config) (*zap.Logger, component.ShutdownFunc, error) { assert.Equal(t, 123, ctx.Value(contextKey{})) assert.Equal(t, settings, set) assert.Equal(t, config, cfg) shutdownFunc := func(context.Context) error { shutdownCalled = true return nil } return dummyLogger, shutdownFunc, errors.New("not implemented") }, )) require.NotNil(t, factory) logger, shutdownFunc, err := factory.CreateLogger(ctx, settings, config) require.EqualError(t, err, "not implemented") assert.Equal(t, dummyLogger, logger) require.NoError(t, shutdownFunc(context.Background())) assert.True(t, shutdownCalled) } func TestNewFactory_CreateMeterProvider(t *testing.T) { type contextKey struct{} var config component.Config = new(struct{}) settings := MeterSettings{ Settings: Settings{BuildInfo: component.NewDefaultBuildInfo()}, } ctx := context.WithValue(context.Background(), contextKey{}, 123) var dummyMeterProvider struct{ MeterProvider } factory := NewFactory(nil, WithCreateMeterProvider( func(ctx context.Context, set MeterSettings, cfg component.Config) (MeterProvider, error) { assert.Equal(t, 123, ctx.Value(contextKey{})) assert.Equal(t, settings, set) assert.Equal(t, config, cfg) return &dummyMeterProvider, errors.New("not implemented") }, )) require.NotNil(t, factory) meterProvider, err := factory.CreateMeterProvider(ctx, settings, config) require.EqualError(t, err, "not implemented") assert.Equal(t, &dummyMeterProvider, meterProvider) } func TestNewFactory_CreateTracerProvider(t *testing.T) { type contextKey struct{} var config component.Config = new(struct{}) settings := TracerSettings{ Settings: Settings{BuildInfo: component.NewDefaultBuildInfo()}, } ctx := context.WithValue(context.Background(), contextKey{}, 123) var dummyTracerProvider struct{ TracerProvider } factory := NewFactory(nil, WithCreateTracerProvider( func(ctx context.Context, set TracerSettings, cfg component.Config) (TracerProvider, error) { assert.Equal(t, 123, ctx.Value(contextKey{})) assert.Equal(t, settings, set) assert.Equal(t, config, cfg) return &dummyTracerProvider, errors.New("not implemented") }, )) require.NotNil(t, factory) tracerProvider, err := factory.CreateTracerProvider(ctx, settings, config) require.EqualError(t, err, "not implemented") assert.Equal(t, &dummyTracerProvider, tracerProvider) } ================================================ FILE: service/telemetry/telemetrytest/Makefile ================================================ include ../../../Makefile.Common ================================================ FILE: service/telemetry/telemetrytest/go.mod ================================================ module go.opentelemetry.io/collector/service/telemetry/telemetrytest 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/service v0.148.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/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/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/grpc-ecosystem/grpc-gateway/v2 v2.28.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/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // 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 go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/collector/config/configtelemetry v0.148.0 // indirect go.opentelemetry.io/collector/featuregate v1.54.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/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/proto/otlp v1.10.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/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/component => ../../../component replace go.opentelemetry.io/collector/internal/telemetry => ../../../internal/telemetry/ replace go.opentelemetry.io/collector/pdata => ../../../pdata replace go.opentelemetry.io/collector/confmap => ../../../confmap replace go.opentelemetry.io/collector/config/configtelemetry => ../../../config/configtelemetry replace go.opentelemetry.io/collector/featuregate => ../../../featuregate replace go.opentelemetry.io/collector/service => ../../ replace go.opentelemetry.io/collector/extension/extensionmiddleware => ../../../extension/extensionmiddleware replace go.opentelemetry.io/collector/config/configmiddleware => ../../../config/configmiddleware replace go.opentelemetry.io/collector/receiver => ../../../receiver replace go.opentelemetry.io/collector/processor/processortest => ../../../processor/processortest replace go.opentelemetry.io/collector/processor/xprocessor => ../../../processor/xprocessor replace go.opentelemetry.io/collector/processor => ../../../processor replace go.opentelemetry.io/collector/pdata/xpdata => ../../../pdata/xpdata replace go.opentelemetry.io/collector/pdata/pprofile => ../../../pdata/pprofile replace go.opentelemetry.io/collector/extension/extensiontest => ../../../extension/extensiontest replace go.opentelemetry.io/collector/exporter/xexporter => ../../../exporter/xexporter replace go.opentelemetry.io/collector/consumer/consumertest => ../../../consumer/consumertest replace go.opentelemetry.io/collector/pipeline => ../../../pipeline replace go.opentelemetry.io/collector/receiver/xreceiver => ../../../receiver/xreceiver replace go.opentelemetry.io/collector/pipeline/xpipeline => ../../../pipeline/xpipeline replace go.opentelemetry.io/collector/pdata/testdata => ../../../pdata/testdata replace go.opentelemetry.io/collector/otelcol => ../../../otelcol replace go.opentelemetry.io/collector/extension/extensioncapabilities => ../../../extension/extensioncapabilities replace go.opentelemetry.io/collector/extension/xextension => ../../../extension/xextension replace go.opentelemetry.io/collector/exporter => ../../../exporter replace go.opentelemetry.io/collector/config/configoptional => ../../../config/configoptional replace go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest => ../../../extension/extensionmiddleware/extensionmiddlewaretest replace go.opentelemetry.io/collector/config/configcompression => ../../../config/configcompression replace go.opentelemetry.io/collector/extension => ../../../extension replace go.opentelemetry.io/collector/exporter/exporterhelper => ../../../exporter/exporterhelper 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/connector => ../../../connector replace go.opentelemetry.io/collector/confmap/xconfmap => ../../../confmap/xconfmap replace go.opentelemetry.io/collector/config/configtls => ../../../config/configtls replace go.opentelemetry.io/collector/service/hostcapabilities => ../../hostcapabilities replace go.opentelemetry.io/collector/extension/zpagesextension => ../../../extension/zpagesextension replace go.opentelemetry.io/collector/config/configretry => ../../../config/configretry replace go.opentelemetry.io/collector/config/confighttp => ../../../config/confighttp replace go.opentelemetry.io/collector/config/configopaque => ../../../config/configopaque replace go.opentelemetry.io/collector/config/configauth => ../../../config/configauth replace go.opentelemetry.io/collector/extension/extensionauth/extensionauthtest => ../../../extension/extensionauth/extensionauthtest replace go.opentelemetry.io/collector/client => ../../../client replace go.opentelemetry.io/collector/receiver/receivertest => ../../../receiver/receivertest replace go.opentelemetry.io/collector/confmap/provider/fileprovider => ../../../confmap/provider/fileprovider replace go.opentelemetry.io/collector/internal/fanoutconsumer => ../../../internal/fanoutconsumer replace go.opentelemetry.io/collector/exporter/exportertest => ../../../exporter/exportertest replace go.opentelemetry.io/collector/extension/extensionauth => ../../../extension/extensionauth replace go.opentelemetry.io/collector/connector/xconnector => ../../../connector/xconnector replace go.opentelemetry.io/collector/connector/connectortest => ../../../connector/connectortest replace go.opentelemetry.io/collector/component/componenttest => ../../../component/componenttest replace go.opentelemetry.io/collector/component/componentstatus => ../../../component/componentstatus 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/telemetry/telemetrytest/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/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.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/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/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/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/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/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/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/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/otelconf v0.22.0 h1:+kpcfczGOFM85zDZyqQCzWefhovegfn24D0WwmQz0n4= go.opentelemetry.io/contrib/otelconf v0.22.0/go.mod h1:ojdbOukO+JRDJQmJY2PRIZEg0UYVzcOuZR59hp7xffc= 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/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= 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/telemetry/telemetrytest/metadata.yaml ================================================ type: service/telemetry/telemetrytest github_project: open-telemetry/opentelemetry-collector status: disable_codecov_badge: true class: pkg ================================================ FILE: service/telemetry/telemetrytest/providers.go ================================================ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package telemetrytest // import "go.opentelemetry.io/collector/service/telemetry/telemetrytest" import ( "context" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/trace" "go.uber.org/zap" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/pdata/pcommon" "go.opentelemetry.io/collector/service/telemetry" ) // WithResource returns a telemetry.FactoryOption that configures the // factory's CreateResource method to return res. func WithResource(res pcommon.Resource) telemetry.FactoryOption { return telemetry.WithCreateResource( func(context.Context, telemetry.Settings, component.Config) (pcommon.Resource, error) { return res, nil }, ) } // WithLogger returns a telemetry.FactoryOption that configures the // factory's CreateLogger method to return logger and shutdown func. func WithLogger(logger *zap.Logger, shutdownFunc component.ShutdownFunc) telemetry.FactoryOption { return telemetry.WithCreateLogger( func(context.Context, telemetry.LoggerSettings, component.Config) ( *zap.Logger, component.ShutdownFunc, error, ) { return logger, shutdownFunc, nil }, ) } // WithMeterProvider returns a telemetry.FactoryOption that configures the // factory's CreateMeterProvider method to return provider. If provider does // not implement the Shutdown method, it will be wrapped with // ShutdownMeterProvider with a no-op shutdown func. func WithMeterProvider(provider metric.MeterProvider) telemetry.FactoryOption { return telemetry.WithCreateMeterProvider( func(context.Context, telemetry.MeterSettings, component.Config) ( telemetry.MeterProvider, error, ) { withShutdown, ok := provider.(telemetry.MeterProvider) if !ok { withShutdown = ShutdownMeterProvider{MeterProvider: provider} } return withShutdown, nil }, ) } // WithTracerProvider returns a telemetry.FactoryOption that configures the // factory's CreateTracerProvider method to return provider. If provider does // not implement the Shutdown method, it will be wrapped with // ShutdownTracerProvider with a no-op shutdown func. func WithTracerProvider(provider trace.TracerProvider) telemetry.FactoryOption { return telemetry.WithCreateTracerProvider( func(context.Context, telemetry.TracerSettings, component.Config) ( telemetry.TracerProvider, error, ) { withShutdown, ok := provider.(telemetry.TracerProvider) if !ok { withShutdown = ShutdownTracerProvider{TracerProvider: provider} } return withShutdown, nil }, ) } type ShutdownMeterProvider struct { metric.MeterProvider component.ShutdownFunc } type ShutdownTracerProvider struct { trace.TracerProvider component.ShutdownFunc } ================================================ FILE: service/telemetry/telemetrytest/providers_test.go ================================================ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package telemetrytest // import "go.opentelemetry.io/collector/service/telemetry/telemetrytest" import ( "context" "errors" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/metric" noopmetric "go.opentelemetry.io/otel/metric/noop" "go.opentelemetry.io/otel/trace" nooptrace "go.opentelemetry.io/otel/trace/noop" "go.uber.org/zap" "go.opentelemetry.io/collector/pdata/pcommon" "go.opentelemetry.io/collector/service/telemetry" ) func TestWithResource(t *testing.T) { res := pcommon.NewResource() res.Attributes().PutStr("key", "value") factory := telemetry.NewFactory(nil, WithResource(res)) createdResource, err := factory.CreateResource(context.Background(), telemetry.Settings{}, nil) require.NoError(t, err) assert.Equal(t, res, createdResource) } func TestWithLogger(t *testing.T) { logger := zap.NewNop() shutdownErr := errors.New("shutdown error") shutdown := func(context.Context) error { return shutdownErr } factory := telemetry.NewFactory(nil, WithLogger(logger, shutdown)) createdLogger, createdShutdown, err := factory.CreateLogger(context.Background(), telemetry.LoggerSettings{}, nil) require.NoError(t, err) assert.Same(t, logger, createdLogger) assert.Same(t, shutdownErr, createdShutdown(t.Context())) } func TestWithMeterProvider(t *testing.T) { test := func(t *testing.T, provider metric.MeterProvider, expected telemetry.MeterProvider) { factory := telemetry.NewFactory(nil, WithMeterProvider(provider)) createdProvider, err := factory.CreateMeterProvider(context.Background(), telemetry.MeterSettings{}, nil) require.NoError(t, err) assert.Equal(t, expected, createdProvider) } t.Run("Without Shutdown method", func(t *testing.T) { provider := noopmetric.NewMeterProvider() test(t, provider, ShutdownMeterProvider{MeterProvider: provider}) }) t.Run("With Shutdown method", func(t *testing.T) { provider := new(struct{ telemetry.MeterProvider }) test(t, provider, provider) }) } func TestWithTracerProvider(t *testing.T) { test := func(t *testing.T, provider trace.TracerProvider, expected telemetry.TracerProvider) { factory := telemetry.NewFactory(nil, WithTracerProvider(provider)) createdProvider, err := factory.CreateTracerProvider(context.Background(), telemetry.TracerSettings{}, nil) require.NoError(t, err) assert.Equal(t, expected, createdProvider) } t.Run("Without Shutdown method", func(t *testing.T) { provider := nooptrace.NewTracerProvider() test(t, provider, ShutdownTracerProvider{TracerProvider: provider}) }) t.Run("With Shutdown method", func(t *testing.T) { provider := new(struct{ telemetry.TracerProvider }) test(t, provider, provider) }) } ================================================ FILE: versions.yaml ================================================ # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 module-sets: stable: version: v1.54.0 modules: - go.opentelemetry.io/collector/client - go.opentelemetry.io/collector/featuregate - go.opentelemetry.io/collector/pdata - 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/httpprovider - go.opentelemetry.io/collector/confmap/provider/httpsprovider - go.opentelemetry.io/collector/confmap/provider/yamlprovider - go.opentelemetry.io/collector/config/configauth - go.opentelemetry.io/collector/config/configopaque - go.opentelemetry.io/collector/config/configoptional - go.opentelemetry.io/collector/config/configcompression - go.opentelemetry.io/collector/config/configretry - go.opentelemetry.io/collector/config/configtls - go.opentelemetry.io/collector/config/confignet - go.opentelemetry.io/collector/config/configmiddleware - go.opentelemetry.io/collector/consumer - go.opentelemetry.io/collector/exporter - go.opentelemetry.io/collector/extension - go.opentelemetry.io/collector/extension/extensionauth - go.opentelemetry.io/collector/pipeline - go.opentelemetry.io/collector/processor - go.opentelemetry.io/collector/receiver beta: version: v0.148.0 modules: - go.opentelemetry.io/collector - go.opentelemetry.io/collector/internal/componentalias - go.opentelemetry.io/collector/internal/memorylimiter - go.opentelemetry.io/collector/internal/fanoutconsumer - go.opentelemetry.io/collector/internal/sharedcomponent - go.opentelemetry.io/collector/internal/telemetry - go.opentelemetry.io/collector/internal/testutil - go.opentelemetry.io/collector/cmd/builder - go.opentelemetry.io/collector/cmd/mdatagen - go.opentelemetry.io/collector/component/componentstatus - go.opentelemetry.io/collector/component/componenttest - go.opentelemetry.io/collector/confmap/xconfmap - go.opentelemetry.io/collector/config/configgrpc - go.opentelemetry.io/collector/config/confighttp - go.opentelemetry.io/collector/config/confighttp/xconfighttp - go.opentelemetry.io/collector/config/configtelemetry - go.opentelemetry.io/collector/connector - go.opentelemetry.io/collector/connector/connectortest - go.opentelemetry.io/collector/connector/forwardconnector - go.opentelemetry.io/collector/connector/xconnector - go.opentelemetry.io/collector/consumer/xconsumer - go.opentelemetry.io/collector/consumer/consumererror - go.opentelemetry.io/collector/consumer/consumererror/xconsumererror - go.opentelemetry.io/collector/consumer/consumertest - go.opentelemetry.io/collector/exporter/debugexporter - go.opentelemetry.io/collector/exporter/exporterhelper - go.opentelemetry.io/collector/exporter/exporterhelper/xexporterhelper - go.opentelemetry.io/collector/exporter/exportertest - go.opentelemetry.io/collector/exporter/nopexporter - go.opentelemetry.io/collector/exporter/otlpexporter - go.opentelemetry.io/collector/exporter/otlphttpexporter - go.opentelemetry.io/collector/exporter/xexporter - go.opentelemetry.io/collector/extension/extensionauth/extensionauthtest - go.opentelemetry.io/collector/extension/extensioncapabilities - go.opentelemetry.io/collector/extension/extensionmiddleware - go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest - go.opentelemetry.io/collector/extension/extensiontest - go.opentelemetry.io/collector/extension/zpagesextension - go.opentelemetry.io/collector/extension/memorylimiterextension - go.opentelemetry.io/collector/extension/xextension - go.opentelemetry.io/collector/otelcol - go.opentelemetry.io/collector/otelcol/otelcoltest - go.opentelemetry.io/collector/pdata/pprofile - go.opentelemetry.io/collector/pdata/testdata - go.opentelemetry.io/collector/pdata/xpdata - go.opentelemetry.io/collector/pipeline/xpipeline - go.opentelemetry.io/collector/processor/processortest - go.opentelemetry.io/collector/processor/processorhelper - go.opentelemetry.io/collector/processor/batchprocessor - go.opentelemetry.io/collector/processor/memorylimiterprocessor - go.opentelemetry.io/collector/processor/processorhelper/xprocessorhelper - go.opentelemetry.io/collector/processor/xprocessor - go.opentelemetry.io/collector/receiver/receiverhelper - go.opentelemetry.io/collector/receiver/nopreceiver - go.opentelemetry.io/collector/receiver/otlpreceiver - go.opentelemetry.io/collector/receiver/receivertest - go.opentelemetry.io/collector/receiver/xreceiver - go.opentelemetry.io/collector/scraper - go.opentelemetry.io/collector/scraper/scraperhelper - go.opentelemetry.io/collector/scraper/scraperhelper/xscraperhelper - go.opentelemetry.io/collector/scraper/scrapertest - go.opentelemetry.io/collector/scraper/xscraper - go.opentelemetry.io/collector/service - go.opentelemetry.io/collector/service/hostcapabilities - go.opentelemetry.io/collector/service/telemetry/telemetrytest - go.opentelemetry.io/collector/filter excluded-modules: - go.opentelemetry.io/collector/cmd/otelcorecol - go.opentelemetry.io/collector/internal/cmd/pdatagen - go.opentelemetry.io/collector/internal/e2e - go.opentelemetry.io/collector/internal/tools - go.opentelemetry.io/collector/confmap/internal/e2e