From f330cfb5d53b685a12854a195cf2b9de673bae7b Mon Sep 17 00:00:00 2001 From: Damien Mathieu <42@dmathieu.com> Date: Fri, 18 Oct 2024 09:48:13 +0200 Subject: [PATCH] Add profiles support to OTLP HTTP exporter (#11450) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### Description This uses the profiles internal packages that were implemented in previous PRs to provide profiles support to the OTLP HTTP exporter. This PR requires #11226✅ to be merged first. --- .chloggen/otlp-http-exporter-profiles.yaml | 25 +++ Makefile | 4 + cmd/builder/internal/builder/main_test.go | 2 + cmd/builder/test/core.builder.yaml | 1 + cmd/otelcorecol/builder-config.yaml | 2 + cmd/otelcorecol/go.mod | 6 + exporter/otlphttpexporter/README.md | 4 +- exporter/otlphttpexporter/factory.go | 37 ++++- exporter/otlphttpexporter/factory_test.go | 12 ++ exporter/otlphttpexporter/go.mod | 13 +- .../internal/metadata/generated_status.go | 7 +- exporter/otlphttpexporter/metadata.yaml | 3 +- exporter/otlphttpexporter/otlp.go | 68 +++++++- exporter/otlphttpexporter/otlp_test.go | 147 +++++++++++++++++- internal/e2e/go.mod | 6 + 15 files changed, 318 insertions(+), 19 deletions(-) create mode 100644 .chloggen/otlp-http-exporter-profiles.yaml diff --git a/.chloggen/otlp-http-exporter-profiles.yaml b/.chloggen/otlp-http-exporter-profiles.yaml new file mode 100644 index 00000000000..5b0e490db9d --- /dev/null +++ b/.chloggen/otlp-http-exporter-profiles.yaml @@ -0,0 +1,25 @@ +# 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. otlpreceiver) +component: otlphttpexporter + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Add profiles support to OTLP HTTP exporter + +# One or more tracking issues or pull requests related to the change +issues: [11450] + +# (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: [api] diff --git a/Makefile b/Makefile index 4d59e1ab6e3..8047437d62e 100644 --- a/Makefile +++ b/Makefile @@ -287,11 +287,13 @@ check-contrib: -replace go.opentelemetry.io/collector/connector/connectorprofiles=$(CURDIR)/connector/connectorprofiles \ -replace go.opentelemetry.io/collector/connector/forwardconnector=$(CURDIR)/connector/forwardconnector \ -replace go.opentelemetry.io/collector/consumer=$(CURDIR)/consumer \ + -replace go.opentelemetry.io/collector/consumer/consumererror/consumererrorprofiles=$(CURDIR)/consumer/consumererror/consumererrorprofiles \ -replace go.opentelemetry.io/collector/consumer/consumerprofiles=$(CURDIR)/consumer/consumerprofiles \ -replace go.opentelemetry.io/collector/consumer/consumertest=$(CURDIR)/consumer/consumertest \ -replace go.opentelemetry.io/collector/exporter=$(CURDIR)/exporter \ -replace go.opentelemetry.io/collector/exporter/debugexporter=$(CURDIR)/exporter/debugexporter \ -replace go.opentelemetry.io/collector/exporter/exporterprofiles=$(CURDIR)/exporter/exporterprofiles \ + -replace go.opentelemetry.io/collector/exporter/exporterhelper/exporterhelperprofiles=$(CURDIR)/exporter/exporterhelper/exporterhelperprofiles \ -replace go.opentelemetry.io/collector/exporter/nopexporter=$(CURDIR)/exporter/nopexporter \ -replace go.opentelemetry.io/collector/exporter/otlpexporter=$(CURDIR)/exporter/otlpexporter \ -replace go.opentelemetry.io/collector/exporter/otlphttpexporter=$(CURDIR)/exporter/otlphttpexporter \ @@ -360,9 +362,11 @@ restore-contrib: -dropreplace go.opentelemetry.io/collector/connector/connectorprofiles \ -dropreplace go.opentelemetry.io/collector/connector/forwardconnector \ -dropreplace go.opentelemetry.io/collector/consumer \ + -dropreplace go.opentelemetry.io/collector/consumer/consumererror/consumererrorprofiles \ -dropreplace go.opentelemetry.io/collector/consumer/consumerprofiles \ -dropreplace go.opentelemetry.io/collector/consumer/consumertest \ -dropreplace go.opentelemetry.io/collector/exporter \ + -dropreplace go.opentelemetry.io/collector/exporter/exporterhelper/exporterhelperprofiles \ -dropreplace go.opentelemetry.io/collector/exporter/debugexporter \ -dropreplace go.opentelemetry.io/collector/exporter/nopexporter \ -dropreplace go.opentelemetry.io/collector/exporter/otlpexporter \ diff --git a/cmd/builder/internal/builder/main_test.go b/cmd/builder/internal/builder/main_test.go index f70fcb03435..f6345cb9639 100644 --- a/cmd/builder/internal/builder/main_test.go +++ b/cmd/builder/internal/builder/main_test.go @@ -60,6 +60,7 @@ var ( "/confmap/provider/httpsprovider", "/confmap/provider/yamlprovider", "/consumer", + "/consumer/consumererror/consumererrorprofiles", "/consumer/consumerprofiles", "/consumer/consumertest", "/connector", @@ -68,6 +69,7 @@ var ( "/exporter", "/exporter/debugexporter", "/exporter/exporterprofiles", + "/exporter/exporterhelper/exporterhelperprofiles", "/exporter/nopexporter", "/exporter/otlpexporter", "/exporter/otlphttpexporter", diff --git a/cmd/builder/test/core.builder.yaml b/cmd/builder/test/core.builder.yaml index e1aa37293d6..b27f92251c6 100644 --- a/cmd/builder/test/core.builder.yaml +++ b/cmd/builder/test/core.builder.yaml @@ -41,6 +41,7 @@ replaces: - go.opentelemetry.io/collector/exporter => ${WORKSPACE_DIR}/exporter - go.opentelemetry.io/collector/exporter/debugexporter => ${WORKSPACE_DIR}/exporter/debugexporter - go.opentelemetry.io/collector/exporter/exporterprofiles => ${WORKSPACE_DIR}/exporter/exporterprofiles + - go.opentelemetry.io/collector/exporter/exporterhelper/exporterhelperprofiles => ${WORKSPACE_DIR}/exporter/exporterhelper/exporterhelperprofiles - go.opentelemetry.io/collector/extension => ${WORKSPACE_DIR}/extension - go.opentelemetry.io/collector/extension/auth => ${WORKSPACE_DIR}/extension/auth - go.opentelemetry.io/collector/extension/experimental/storage => ${WORKSPACE_DIR}/extension/experimental/storage diff --git a/cmd/otelcorecol/builder-config.yaml b/cmd/otelcorecol/builder-config.yaml index debbb1bc9ba..c0f595540fb 100644 --- a/cmd/otelcorecol/builder-config.yaml +++ b/cmd/otelcorecol/builder-config.yaml @@ -60,6 +60,7 @@ replaces: - go.opentelemetry.io/collector/confmap/provider/yamlprovider => ../../confmap/provider/yamlprovider - go.opentelemetry.io/collector/consumer => ../../consumer - go.opentelemetry.io/collector/consumer/consumerprofiles => ../../consumer/consumerprofiles + - go.opentelemetry.io/collector/consumer/consumererror/consumererrorprofiles => ../../consumer/consumererror/consumererrorprofiles - go.opentelemetry.io/collector/consumer/consumertest => ../../consumer/consumertest - go.opentelemetry.io/collector/connector => ../../connector - go.opentelemetry.io/collector/connector/connectortest => ../../connector/connectortest @@ -68,6 +69,7 @@ replaces: - go.opentelemetry.io/collector/exporter => ../../exporter - go.opentelemetry.io/collector/exporter/debugexporter => ../../exporter/debugexporter - go.opentelemetry.io/collector/exporter/exporterprofiles => ../../exporter/exporterprofiles + - go.opentelemetry.io/collector/exporter/exporterhelper/exporterhelperprofiles => ../../exporter/exporterhelper/exporterhelperprofiles - go.opentelemetry.io/collector/exporter/nopexporter => ../../exporter/nopexporter - go.opentelemetry.io/collector/exporter/otlpexporter => ../../exporter/otlpexporter - go.opentelemetry.io/collector/exporter/otlphttpexporter => ../../exporter/otlphttpexporter diff --git a/cmd/otelcorecol/go.mod b/cmd/otelcorecol/go.mod index e47430f4cf5..22e606e328a 100644 --- a/cmd/otelcorecol/go.mod +++ b/cmd/otelcorecol/go.mod @@ -95,8 +95,10 @@ require ( go.opentelemetry.io/collector/connector/connectorprofiles v0.111.0 // indirect go.opentelemetry.io/collector/connector/connectortest v0.111.0 // indirect go.opentelemetry.io/collector/consumer v0.111.0 // indirect + go.opentelemetry.io/collector/consumer/consumererror/consumererrorprofiles v0.0.0-00010101000000-000000000000 // indirect go.opentelemetry.io/collector/consumer/consumerprofiles v0.111.0 // indirect go.opentelemetry.io/collector/consumer/consumertest v0.111.0 // indirect + go.opentelemetry.io/collector/exporter/exporterhelper/exporterhelperprofiles v0.0.0-00010101000000-000000000000 // indirect go.opentelemetry.io/collector/exporter/exporterprofiles v0.111.0 // indirect go.opentelemetry.io/collector/extension/auth v0.111.0 // indirect go.opentelemetry.io/collector/extension/experimental/storage v0.111.0 // indirect @@ -193,6 +195,8 @@ replace go.opentelemetry.io/collector/consumer => ../../consumer replace go.opentelemetry.io/collector/consumer/consumerprofiles => ../../consumer/consumerprofiles +replace go.opentelemetry.io/collector/consumer/consumererror/consumererrorprofiles => ../../consumer/consumererror/consumererrorprofiles + replace go.opentelemetry.io/collector/consumer/consumertest => ../../consumer/consumertest replace go.opentelemetry.io/collector/connector => ../../connector @@ -209,6 +213,8 @@ replace go.opentelemetry.io/collector/exporter/debugexporter => ../../exporter/d replace go.opentelemetry.io/collector/exporter/exporterprofiles => ../../exporter/exporterprofiles +replace go.opentelemetry.io/collector/exporter/exporterhelper/exporterhelperprofiles => ../../exporter/exporterhelper/exporterhelperprofiles + replace go.opentelemetry.io/collector/exporter/nopexporter => ../../exporter/nopexporter replace go.opentelemetry.io/collector/exporter/otlpexporter => ../../exporter/otlpexporter diff --git a/exporter/otlphttpexporter/README.md b/exporter/otlphttpexporter/README.md index fbc58e4c9a2..422347d9d6d 100644 --- a/exporter/otlphttpexporter/README.md +++ b/exporter/otlphttpexporter/README.md @@ -3,11 +3,13 @@ | Status | | | ------------- |-----------| -| Stability | [beta]: logs | +| Stability | [development]: profiles | +| | [beta]: logs | | | [stable]: traces, metrics | | 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%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) | +[development]: https://github.com/open-telemetry/opentelemetry-collector#development [beta]: https://github.com/open-telemetry/opentelemetry-collector#beta [stable]: https://github.com/open-telemetry/opentelemetry-collector#stable [core]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol diff --git a/exporter/otlphttpexporter/factory.go b/exporter/otlphttpexporter/factory.go index 78940cbfb42..c3a91a44bb9 100644 --- a/exporter/otlphttpexporter/factory.go +++ b/exporter/otlphttpexporter/factory.go @@ -17,17 +17,20 @@ import ( "go.opentelemetry.io/collector/consumer" "go.opentelemetry.io/collector/exporter" "go.opentelemetry.io/collector/exporter/exporterhelper" + "go.opentelemetry.io/collector/exporter/exporterhelper/exporterhelperprofiles" + "go.opentelemetry.io/collector/exporter/exporterprofiles" "go.opentelemetry.io/collector/exporter/otlphttpexporter/internal/metadata" ) // NewFactory creates a factory for OTLP exporter. func NewFactory() exporter.Factory { - return exporter.NewFactory( + return exporterprofiles.NewFactory( metadata.Type, createDefaultConfig, - exporter.WithTraces(createTraces, metadata.TracesStability), - exporter.WithMetrics(createMetrics, metadata.MetricsStability), - exporter.WithLogs(createLogs, metadata.LogsStability), + exporterprofiles.WithTraces(createTraces, metadata.TracesStability), + exporterprofiles.WithMetrics(createMetrics, metadata.MetricsStability), + exporterprofiles.WithLogs(createLogs, metadata.LogsStability), + exporterprofiles.WithProfiles(createProfiles, metadata.ProfilesStability), ) } @@ -146,3 +149,29 @@ func createLogs( exporterhelper.WithRetry(oCfg.RetryConfig), exporterhelper.WithQueue(oCfg.QueueConfig)) } + +func createProfiles( + ctx context.Context, + set exporter.Settings, + cfg component.Config, +) (exporterprofiles.Profiles, error) { + oce, err := newExporter(cfg, set) + if err != nil { + return nil, err + } + oCfg := cfg.(*Config) + + oce.profilesURL, err = composeSignalURL(oCfg, "", "profiles", "v1development") + if err != nil { + return nil, err + } + + return exporterhelperprofiles.NewProfilesExporter(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)) +} diff --git a/exporter/otlphttpexporter/factory_test.go b/exporter/otlphttpexporter/factory_test.go index e56a30c0980..9c3918b2063 100644 --- a/exporter/otlphttpexporter/factory_test.go +++ b/exporter/otlphttpexporter/factory_test.go @@ -17,6 +17,7 @@ import ( "go.opentelemetry.io/collector/config/confighttp" "go.opentelemetry.io/collector/config/configopaque" "go.opentelemetry.io/collector/config/configtls" + "go.opentelemetry.io/collector/exporter/exporterprofiles" "go.opentelemetry.io/collector/exporter/exportertest" "go.opentelemetry.io/collector/internal/testutil" ) @@ -198,6 +199,17 @@ func TestCreateLogs(t *testing.T) { 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() + oexp, err := factory.(exporterprofiles.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) diff --git a/exporter/otlphttpexporter/go.mod b/exporter/otlphttpexporter/go.mod index 1029e8d4b9d..2f7b574d2f6 100644 --- a/exporter/otlphttpexporter/go.mod +++ b/exporter/otlphttpexporter/go.mod @@ -14,7 +14,10 @@ require ( go.opentelemetry.io/collector/confmap v1.17.0 go.opentelemetry.io/collector/consumer v0.111.0 go.opentelemetry.io/collector/exporter v0.111.0 + go.opentelemetry.io/collector/exporter/exporterhelper/exporterhelperprofiles v0.0.0-00010101000000-000000000000 + go.opentelemetry.io/collector/exporter/exporterprofiles v0.111.0 go.opentelemetry.io/collector/pdata v1.17.0 + go.opentelemetry.io/collector/pdata/pprofile v0.111.0 go.uber.org/goleak v1.3.0 go.uber.org/zap v1.27.0 google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd @@ -49,14 +52,14 @@ require ( go.opentelemetry.io/collector/config/configauth v0.111.0 // indirect go.opentelemetry.io/collector/config/configtelemetry v0.111.0 // indirect go.opentelemetry.io/collector/config/internal v0.111.0 // indirect + go.opentelemetry.io/collector/consumer/consumererror/consumererrorprofiles v0.0.0-00010101000000-000000000000 // indirect go.opentelemetry.io/collector/consumer/consumerprofiles v0.111.0 // indirect go.opentelemetry.io/collector/consumer/consumertest v0.111.0 // indirect - go.opentelemetry.io/collector/exporter/exporterprofiles v0.111.0 // indirect go.opentelemetry.io/collector/extension v0.111.0 // indirect go.opentelemetry.io/collector/extension/auth v0.111.0 // indirect go.opentelemetry.io/collector/extension/experimental/storage v0.111.0 // indirect - go.opentelemetry.io/collector/pdata/pprofile v0.111.0 // indirect go.opentelemetry.io/collector/pipeline v0.111.0 // indirect + go.opentelemetry.io/collector/pipeline/pipelineprofiles v0.0.0-00010101000000-000000000000 // indirect go.opentelemetry.io/collector/receiver v0.111.0 // indirect go.opentelemetry.io/collector/receiver/receiverprofiles v0.111.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 // indirect @@ -122,6 +125,12 @@ replace go.opentelemetry.io/collector/component/componentstatus => ../../compone replace go.opentelemetry.io/collector/receiver/receiverprofiles => ../../receiver/receiverprofiles +replace go.opentelemetry.io/collector/pipeline/pipelineprofiles => ../../pipeline/pipelineprofiles + +replace go.opentelemetry.io/collector/consumer/consumererror/consumererrorprofiles => ../../consumer/consumererror/consumererrorprofiles + +replace go.opentelemetry.io/collector/exporter/exporterhelper/exporterhelperprofiles => ../exporterhelper/exporterhelperprofiles + replace go.opentelemetry.io/collector/exporter/exporterprofiles => ../exporterprofiles replace go.opentelemetry.io/collector/pipeline => ../../pipeline diff --git a/exporter/otlphttpexporter/internal/metadata/generated_status.go b/exporter/otlphttpexporter/internal/metadata/generated_status.go index c38e6de2550..07bd3d1d7a6 100644 --- a/exporter/otlphttpexporter/internal/metadata/generated_status.go +++ b/exporter/otlphttpexporter/internal/metadata/generated_status.go @@ -12,7 +12,8 @@ var ( ) const ( - LogsStability = component.StabilityLevelBeta - TracesStability = component.StabilityLevelStable - MetricsStability = component.StabilityLevelStable + ProfilesStability = component.StabilityLevelDevelopment + LogsStability = component.StabilityLevelBeta + TracesStability = component.StabilityLevelStable + MetricsStability = component.StabilityLevelStable ) diff --git a/exporter/otlphttpexporter/metadata.yaml b/exporter/otlphttpexporter/metadata.yaml index 6d4b1bcba34..9b6498b8c04 100644 --- a/exporter/otlphttpexporter/metadata.yaml +++ b/exporter/otlphttpexporter/metadata.yaml @@ -6,9 +6,10 @@ status: stability: stable: [traces, metrics] beta: [logs] + development: [profiles] distributions: [core, contrib, k8s] tests: config: endpoint: "https://1.2.3.4:1234" - + diff --git a/exporter/otlphttpexporter/otlp.go b/exporter/otlphttpexporter/otlp.go index 5c88b53c83f..0decd18315a 100644 --- a/exporter/otlphttpexporter/otlp.go +++ b/exporter/otlphttpexporter/otlp.go @@ -28,19 +28,22 @@ import ( "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 - logger *zap.Logger - settings component.TelemetrySettings + 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 } @@ -149,6 +152,27 @@ func (e *baseExporter) pushLogs(ctx context.Context, ld plog.Logs) error { 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, url string, request []byte, partialSuccessHandler partialSuccessHandler) error { e.logger.Debug("Preparing to make HTTP request", zap.String("url", url)) req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(request)) @@ -392,3 +416,33 @@ func (e *baseExporter) logsPartialSuccessHandler(protoBytes []byte, contentType } 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 +} diff --git a/exporter/otlphttpexporter/otlp_test.go b/exporter/otlphttpexporter/otlp_test.go index 20c183cf158..92b76df3bf0 100644 --- a/exporter/otlphttpexporter/otlp_test.go +++ b/exporter/otlphttpexporter/otlp_test.go @@ -33,6 +33,8 @@ import ( "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" ) @@ -40,6 +42,7 @@ import ( const tracesTelemetryType = "traces" const metricsTelemetryType = "metrics" const logsTelemetryType = "logs" +const profilesTelemetryType = "profiles" type responseSerializer interface { MarshalJSON() ([]byte, error) @@ -72,6 +75,14 @@ func provideLogsResponseSerializer() responseSerializer { 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) @@ -381,6 +392,40 @@ func TestUserAgent(t *testing.T) { }) } }) + + 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(200) + }) + 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) { @@ -404,6 +449,10 @@ func TestPartialSuccessInvalidBody(t *testing.T) { 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) { @@ -428,7 +477,7 @@ func TestPartialSuccessUnsupportedContentType(t *testing.T) { contentType: "application/octet-stream", }, } - for _, telemetryType := range []string{"logs", "metrics", "traces"} { + 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 @@ -439,6 +488,8 @@ func TestPartialSuccessUnsupportedContentType(t *testing.T) { handler = exp.metricsPartialSuccessHandler case "traces": handler = exp.tracesPartialSuccessHandler + case "profiles": + handler = exp.profilesPartialSuccessHandler default: panic(telemetryType) } @@ -529,6 +580,11 @@ func TestPartialResponse_missingHeaderButHasBody(t *testing.T) { handler: exp.logsPartialSuccessHandler, serializer: provideLogsResponseSerializer, }, + { + telemetryType: profilesTelemetryType, + handler: exp.profilesPartialSuccessHandler, + serializer: provideProfilesResponseSerializer, + }, } for _, ct := range contentTypes { @@ -593,6 +649,10 @@ func TestPartialResponse_missingHeaderAndBody(t *testing.T) { telemetryType: logsTelemetryType, handler: exp.logsPartialSuccessHandler, }, + { + telemetryType: profilesTelemetryType, + handler: exp.profilesPartialSuccessHandler, + }, } for _, ct := range contentTypes { @@ -661,6 +721,11 @@ func TestPartialSuccess_shortContentLengthHeader(t *testing.T) { handler: exp.logsPartialSuccessHandler, serializer: provideLogsResponseSerializer, }, + { + telemetryType: profilesTelemetryType, + handler: exp.profilesPartialSuccessHandler, + serializer: provideProfilesResponseSerializer, + }, } for _, ct := range contentTypes { @@ -720,6 +785,10 @@ func TestPartialSuccess_longContentLengthHeader(t *testing.T) { telemetryType: logsTelemetryType, serializer: provideLogsResponseSerializer, }, + { + telemetryType: profilesTelemetryType, + serializer: provideProfilesResponseSerializer, + }, } for _, ct := range contentTypes { @@ -743,6 +812,8 @@ func TestPartialSuccess_longContentLengthHeader(t *testing.T) { handler = exp.metricsPartialSuccessHandler case logsTelemetryType: handler = exp.logsPartialSuccessHandler + case profilesTelemetryType: + handler = exp.profilesPartialSuccessHandler default: require.Fail(t, "unsupported telemetry type: %s", ct.contentType) } @@ -874,6 +945,47 @@ func TestPartialSuccess_metrics(t *testing.T) { 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() + logger, observed := observer.New(zap.DebugLevel) + set.TelemetrySettings.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() set.BuildInfo.Description = "Collector" @@ -990,6 +1102,39 @@ func TestEncoding(t *testing.T) { }) } }) + + 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(200) + }) + 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 { diff --git a/internal/e2e/go.mod b/internal/e2e/go.mod index 0f4a764daff..88cad5b2421 100644 --- a/internal/e2e/go.mod +++ b/internal/e2e/go.mod @@ -79,7 +79,9 @@ require ( go.opentelemetry.io/collector/config/confignet v1.17.0 // indirect go.opentelemetry.io/collector/config/internal v0.111.0 // indirect go.opentelemetry.io/collector/connector/connectorprofiles v0.111.0 // indirect + go.opentelemetry.io/collector/consumer/consumererror/consumererrorprofiles v0.0.0-00010101000000-000000000000 // indirect go.opentelemetry.io/collector/consumer/consumerprofiles v0.111.0 // indirect + go.opentelemetry.io/collector/exporter/exporterhelper/exporterhelperprofiles v0.0.0-00010101000000-000000000000 // indirect go.opentelemetry.io/collector/exporter/exporterprofiles v0.111.0 // indirect go.opentelemetry.io/collector/extension/auth v0.111.0 // indirect go.opentelemetry.io/collector/extension/experimental/storage v0.111.0 // indirect @@ -213,3 +215,7 @@ replace go.opentelemetry.io/collector/pipeline => ../../pipeline replace go.opentelemetry.io/collector/pipeline/pipelineprofiles => ../../pipeline/pipelineprofiles replace go.opentelemetry.io/collector/processor/processortest => ../../processor/processortest + +replace go.opentelemetry.io/collector/consumer/consumererror/consumererrorprofiles => ../../consumer/consumererror/consumererrorprofiles + +replace go.opentelemetry.io/collector/exporter/exporterhelper/exporterhelperprofiles => ../../exporter/exporterhelper/exporterhelperprofiles