From 033db43bc13cdfea360256655f056d40c518b836 Mon Sep 17 00:00:00 2001 From: Antoine Toulme Date: Tue, 18 Nov 2025 14:57:33 -0800 Subject: [PATCH 1/3] [receiver/pprof] attempt to implement functionality of receiving pprof data and converting it to profiles --- .chloggen/attempt_to_convert_pprof.yaml | 27 ++++++ .../pprof/pprof_to_profiles_test.go | 2 +- receiver/pprofreceiver/README.md | 14 ++- receiver/pprofreceiver/config.go | 14 ++- receiver/pprofreceiver/factory.go | 94 ++++++++++++++++--- receiver/pprofreceiver/factory_test.go | 36 +++++++ receiver/pprofreceiver/go.mod | 40 +++----- receiver/pprofreceiver/go.sum | 74 +++------------ 8 files changed, 198 insertions(+), 103 deletions(-) create mode 100644 .chloggen/attempt_to_convert_pprof.yaml create mode 100644 receiver/pprofreceiver/factory_test.go diff --git a/.chloggen/attempt_to_convert_pprof.yaml b/.chloggen/attempt_to_convert_pprof.yaml new file mode 100644 index 0000000000000..caf41c7f2e3b0 --- /dev/null +++ b/.chloggen/attempt_to_convert_pprof.yaml @@ -0,0 +1,27 @@ +# Use this changelog template to create an entry for release notes. + +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: new_component + +# The name of the component, or a single word describing the area of concern, (e.g. receiver/filelog) +component: receiver/pprof + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Implement receiving pprof data and converting it to profile data + +# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. +issues: [38260] + +# (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: + +# If your change doesn't affect end users or the exported elements of any package, +# you should instead start your pull request title with [chore] or use the "Skip Changelog" label. +# 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: [] diff --git a/pkg/translator/pprof/pprof_to_profiles_test.go b/pkg/translator/pprof/pprof_to_profiles_test.go index c23d452cb47a4..f5841a543ad53 100644 --- a/pkg/translator/pprof/pprof_to_profiles_test.go +++ b/pkg/translator/pprof/pprof_to_profiles_test.go @@ -34,7 +34,7 @@ func TestConvertPprofToPprofile(t *testing.T) { p, err := profile.Parse(bytes.NewBuffer(inbytes)) require.NoError(t, err) - pprofile, err := convertPprofToPprofile(p) + pprofile, err := ConvertPprofToPprofile(p) switch { case errors.Is(err, tc.expectedError): // The expected error equals the returned error, diff --git a/receiver/pprofreceiver/README.md b/receiver/pprofreceiver/README.md index 86c536c81d248..b62608b887a94 100644 --- a/receiver/pprofreceiver/README.md +++ b/receiver/pprofreceiver/README.md @@ -12,4 +12,16 @@ [development]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#development -TODO \ No newline at end of file +This receiver interprets pprof profiles. + +Note this receiver is incompatible with the pprof extension. + +The receiver will capture pprof information from the collector itself or a remote Go program exposing an endpoint. + +Collector +```yaml +receivers: + pprof: +``` + +In a future iteration, this receiver will support collecting pprof data from a remote pprof endpoint. diff --git a/receiver/pprofreceiver/config.go b/receiver/pprofreceiver/config.go index 3cc74ddc8dafd..9958f8fc37a93 100644 --- a/receiver/pprofreceiver/config.go +++ b/receiver/pprofreceiver/config.go @@ -3,8 +3,18 @@ package pprofreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/pprofreceiver" -import "go.opentelemetry.io/collector/config/confighttp" +import "time" type Config struct { - confighttp.ClientConfig `mapstructure:",squash"` + // CollectionInterval sets how frequently profiles should be collected. + CollectionInterval time.Duration `mapstructure:"collection_interval"` + + // Fraction of blocking events that are profiled. A value <= 0 disables + // profiling. See https://golang.org/pkg/runtime/#SetBlockProfileRate for details. + BlockProfileFraction int `mapstructure:"block_profile_fraction"` + + // Fraction of mutex contention events that are profiled. A value <= 0 + // disables profiling. See https://golang.org/pkg/runtime/#SetMutexProfileFraction + // for details. + MutexProfileFraction int `mapstructure:"mutex_profile_fraction"` } diff --git a/receiver/pprofreceiver/factory.go b/receiver/pprofreceiver/factory.go index 3b8b7b6dbd061..de7f49c3d83fc 100644 --- a/receiver/pprofreceiver/factory.go +++ b/receiver/pprofreceiver/factory.go @@ -4,15 +4,22 @@ package pprofreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/pprofreceiver" import ( + "bufio" + "bytes" "context" - "errors" + "runtime" + "runtime/pprof" + "time" + "github.com/google/pprof/profile" "go.opentelemetry.io/collector/component" - "go.opentelemetry.io/collector/config/confighttp" "go.opentelemetry.io/collector/consumer/xconsumer" + "go.opentelemetry.io/collector/pdata/pprofile" "go.opentelemetry.io/collector/receiver" "go.opentelemetry.io/collector/receiver/xreceiver" + "go.uber.org/zap" + pproftranslator "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/pprof" "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/pprofreceiver/internal/metadata" ) @@ -25,25 +32,90 @@ func NewFactory() receiver.Factory { func createDefaultConfig() component.Config { return &Config{ - ClientConfig: confighttp.NewDefaultClientConfig(), + CollectionInterval: 10 * time.Second, } } func createProfilesReceiver( - context.Context, - receiver.Settings, - component.Config, - xconsumer.Profiles, + _ context.Context, + set receiver.Settings, + cfg component.Config, + c xconsumer.Profiles, ) (xreceiver.Profiles, error) { - return &rcvr{}, errors.New("not implemented") + return &pprofReceiver{ + consumer: c, + telemetrySettings: set.TelemetrySettings, + config: cfg.(*Config), + done: make(chan struct{}), + }, nil } -type rcvr struct{} +type pprofReceiver struct { + consumer xconsumer.Profiles + telemetrySettings component.TelemetrySettings + config *Config + done chan struct{} +} + +func (r *pprofReceiver) convert(p []byte) (pprofile.Profiles, error) { + parsed, err := profile.ParseData(p) + if err != nil { + r.telemetrySettings.Logger.Debug("failed to parse pprof profile", zap.Error(err)) + return pprofile.Profiles{}, err + } + pp, err := pproftranslator.ConvertPprofToPprofile(parsed) + if err != nil { + r.telemetrySettings.Logger.Debug("failed to convert pprof profile", zap.Error(err)) + return pprofile.Profiles{}, err + } + return *pp, nil +} + +func (r *pprofReceiver) Start(_ context.Context, _ component.Host) error { + runtime.SetBlockProfileRate(r.config.BlockProfileFraction) + runtime.SetMutexProfileFraction(r.config.MutexProfileFraction) + + go func() { + timer := time.NewTicker(r.config.CollectionInterval) + collecting := false + payload := make([]byte, 0, 8096) + buf := bytes.NewBuffer(payload) + writer := bufio.NewWriter(buf) + for { + select { + case <-r.done: + pprof.StopCPUProfile() + return + case <-timer.C: + if collecting { + pprof.StopCPUProfile() + _ = writer.Flush() + collecting = false + pp, err := r.convert(buf.Bytes()) + buf.Reset() + if err != nil { + r.telemetrySettings.Logger.Error("error processing profile", zap.Error(err)) + } else { + err = r.consumer.ConsumeProfiles(context.Background(), pp) + if err != nil { + r.telemetrySettings.Logger.Error("failed to ingest pprof profile", zap.Error(err)) + } + } + } else { + err := pprof.StartCPUProfile(writer) + if err != nil { + r.telemetrySettings.Logger.Error("failed to start CPU profile", zap.Error(err)) + } + collecting = true + } + } + } + }() -func (rcvr) Start(context.Context, component.Host) error { return nil } -func (rcvr) Shutdown(context.Context) error { +func (r *pprofReceiver) Shutdown(_ context.Context) error { + close(r.done) return nil } diff --git a/receiver/pprofreceiver/factory_test.go b/receiver/pprofreceiver/factory_test.go new file mode 100644 index 0000000000000..5602931efd2e0 --- /dev/null +++ b/receiver/pprofreceiver/factory_test.go @@ -0,0 +1,36 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package pprofreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/pprofreceiver" +import ( + "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/receiver/receivertest" + "go.opentelemetry.io/collector/receiver/xreceiver" + "go.uber.org/zap" +) + +func TestStartStop(t *testing.T) { + f := NewFactory().(xreceiver.Factory) + profilesConsumer := new(consumertest.ProfilesSink) + set := receivertest.NewNopSettings(f.Type()) + var err error + set.Logger, err = zap.NewDevelopment() + require.NoError(t, err) + cfg := f.CreateDefaultConfig().(*Config) + cfg.CollectionInterval = 1 * time.Second + r, err := f.CreateProfiles(t.Context(), set, cfg, profilesConsumer) + require.NoError(t, err) + err = r.Start(t.Context(), componenttest.NewNopHost()) + require.NoError(t, err) + require.EventuallyWithT(t, func(tt *assert.CollectT) { + require.NotEmpty(tt, profilesConsumer.AllProfiles()) + }, 5*time.Second, 100*time.Millisecond, "failed to receive data from pprof receiver") + err = r.Shutdown(t.Context()) + require.NoError(t, err) +} diff --git a/receiver/pprofreceiver/go.mod b/receiver/pprofreceiver/go.mod index 3f10a22beb364..fcfa834949ee5 100644 --- a/receiver/pprofreceiver/go.mod +++ b/receiver/pprofreceiver/go.mod @@ -3,34 +3,33 @@ module github.com/open-telemetry/opentelemetry-collector-contrib/receiver/pprofr go 1.24.0 require ( + github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8 + github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/pprof v0.145.0 github.com/stretchr/testify v1.11.1 go.opentelemetry.io/collector/component v1.51.0 go.opentelemetry.io/collector/component/componenttest v0.145.0 - go.opentelemetry.io/collector/config/confighttp v0.145.0 go.opentelemetry.io/collector/confmap v1.51.0 + go.opentelemetry.io/collector/consumer/consumertest v0.145.0 go.opentelemetry.io/collector/consumer/xconsumer v0.145.0 + go.opentelemetry.io/collector/pdata/pprofile v0.145.0 go.opentelemetry.io/collector/receiver v1.51.0 go.opentelemetry.io/collector/receiver/receivertest v0.145.0 go.opentelemetry.io/collector/receiver/xreceiver v0.145.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.3 // indirect + github.com/klauspost/cpuid/v2 v2.2.10 // 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.2 // indirect @@ -38,41 +37,24 @@ require ( 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.23 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/rs/cors v1.11.1 // indirect + github.com/zeebo/xxh3 v1.1.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/collector/client v1.51.0 // indirect - go.opentelemetry.io/collector/config/configauth v1.51.0 // indirect - go.opentelemetry.io/collector/config/configcompression v1.51.0 // indirect - go.opentelemetry.io/collector/config/configmiddleware v1.51.0 // indirect - go.opentelemetry.io/collector/config/confignet v1.51.0 // indirect - go.opentelemetry.io/collector/config/configopaque v1.51.0 // indirect - go.opentelemetry.io/collector/config/configoptional v1.51.0 // indirect - go.opentelemetry.io/collector/config/configtls v1.51.0 // indirect - go.opentelemetry.io/collector/confmap/xconfmap v0.145.0 // indirect go.opentelemetry.io/collector/consumer v1.51.0 // indirect go.opentelemetry.io/collector/consumer/consumererror v0.145.0 // indirect - go.opentelemetry.io/collector/extension/extensionauth v1.51.0 // indirect - go.opentelemetry.io/collector/extension/extensionmiddleware v0.145.0 // indirect go.opentelemetry.io/collector/featuregate v1.51.0 // indirect go.opentelemetry.io/collector/internal/componentalias v0.145.0 // indirect go.opentelemetry.io/collector/pdata v1.51.0 // indirect - go.opentelemetry.io/collector/pdata/pprofile v0.145.0 // indirect go.opentelemetry.io/collector/pipeline v1.51.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect - go.opentelemetry.io/otel v1.39.0 // indirect - go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/otel v1.40.0 // indirect + go.opentelemetry.io/otel/metric v1.40.0 // indirect go.opentelemetry.io/otel/sdk v1.39.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.39.0 // indirect - go.opentelemetry.io/otel/trace v1.39.0 // indirect + go.opentelemetry.io/otel/trace v1.40.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.47.0 // indirect golang.org/x/net v0.49.0 // indirect golang.org/x/sys v0.40.0 // indirect - golang.org/x/text v0.33.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect google.golang.org/grpc v1.78.0 // indirect google.golang.org/protobuf v1.36.11 // indirect @@ -84,3 +66,5 @@ retract ( v0.76.1 v0.65.0 ) + +replace github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/pprof => ../../pkg/translator/pprof diff --git a/receiver/pprofreceiver/go.sum b/receiver/pprofreceiver/go.sum index bf411c16a7655..2022562a3be01 100644 --- a/receiver/pprofreceiver/go.sum +++ b/receiver/pprofreceiver/go.sum @@ -3,12 +3,6 @@ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL 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= @@ -20,23 +14,19 @@ 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/pprof v0.0.0-20251114195745-4902fdda35c8 h1:3DsUAV+VNEQa2CUVLxCY3f87278uWfIDhJnbdvDjvmE= +github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U= 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.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw= -github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= +github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= +github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= 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= @@ -57,46 +47,26 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ 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.23 h1:oJE7T90aYBGtFNrI8+KbETnPymobAhzRrR8Mu8n1yfU= -github.com/pierrec/lz4/v4 v4.1.23/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= +github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= +github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= 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/collector/client v1.51.0 h1:7FaC2gglA7OWol/wMMSpoE1nFY6oewIIyf3nqVzO8m8= -go.opentelemetry.io/collector/client v1.51.0/go.mod h1:lx+VIlIm1/qaUeWs4ozeV/Q9y9rJQGwQo+dnk+We5TQ= go.opentelemetry.io/collector/component v1.51.0 h1:btNW76MCRmpsk0ARRT5wspDXF9tvdaLd3uBtYXIiQn0= go.opentelemetry.io/collector/component v1.51.0/go.mod h1:Zlgwh4yTLDhJglOXqiyXZ7paepTvvoijfFjLqOr/Qww= go.opentelemetry.io/collector/component/componenttest v0.145.0 h1:ryhRrXqQybGMhz7A7t32NC8BXAFcX2o1RetgPM7vw88= go.opentelemetry.io/collector/component/componenttest v0.145.0/go.mod h1:5uStrhUdZ0Fw3se00CPmVaRtW8o9N8kKiY76OSCWFjQ= -go.opentelemetry.io/collector/config/configauth v1.51.0 h1:89pjoUxbmUGURr8PyaxowuIlISrBkwJUbr/JhCpL4EI= -go.opentelemetry.io/collector/config/configauth v1.51.0/go.mod h1:RXorbqKrG63mBLglhvH+A1Gn9R74JH/agPC31goV33Y= -go.opentelemetry.io/collector/config/configcompression v1.51.0 h1:kqLzehPPinndkt2M5axkzxOKSgHZwVTrcIfuTQ9itpw= -go.opentelemetry.io/collector/config/configcompression v1.51.0/go.mod h1:ZlnKaXFYL3HVMUNWVAo/YOLYoxNZo7h8SrQp3l7GV00= -go.opentelemetry.io/collector/config/confighttp v0.145.0 h1:H7EI4JanJsf1bg5A8pDP7XPSeiLjlqiOvGtqX1yj2JI= -go.opentelemetry.io/collector/config/confighttp v0.145.0/go.mod h1:/kPeMrfsnzdXQwxC6q8sjedesX+FQSupJe79BnFOUWI= -go.opentelemetry.io/collector/config/configmiddleware v1.51.0 h1:AMZP9+LgFoAdfNTkx+qfFPqBiQY3k8yCigjv6HUbGe0= -go.opentelemetry.io/collector/config/configmiddleware v1.51.0/go.mod h1:37G0+KEiJf0ZYw4q2euslxkx1WaKun//KV8vaw1HkRA= -go.opentelemetry.io/collector/config/confignet v1.51.0 h1:gEIPVPbboYi/ESt2WyfZBPjtrM2zPnKJX2shmNUbtok= -go.opentelemetry.io/collector/config/confignet v1.51.0/go.mod h1:4jJWdoe1MmpqxMzxrIILcS5FK2JPocXYZGUvv5ZQVKE= -go.opentelemetry.io/collector/config/configopaque v1.51.0 h1:z8Q72mBMQ6P4me+umu1kCC3sqzX+zQ7OJju5oQcdZv8= -go.opentelemetry.io/collector/config/configopaque v1.51.0/go.mod h1:w77VAty/J8dxrSyq0ObbvQxh+xh0tVg+SQqFQ7SQRzM= -go.opentelemetry.io/collector/config/configoptional v1.51.0 h1:kVD8B3JF0Hd5LrRhHIKXAcHeTbQk9cxa0nD06IgJ+Gs= -go.opentelemetry.io/collector/config/configoptional v1.51.0/go.mod h1:nBG71pzrklmiPIp1XPQiO3RzlbLIolUlFrW30q1UXzM= -go.opentelemetry.io/collector/config/configtls v1.51.0 h1:fkZ3o3i6A7MCQBYCid2ZBYgaE3bYWpr3EognX09C1Tc= -go.opentelemetry.io/collector/config/configtls v1.51.0/go.mod h1:d2yeGb0Bt0WA9cL9SpC1nfhu5Qfiz+PhtQoecs+Kong= go.opentelemetry.io/collector/confmap v1.51.0 h1:C9YlMNkIgzuauLpUz2F7DLlWwqAmkQKNcKj1XATVWuE= go.opentelemetry.io/collector/confmap v1.51.0/go.mod h1:uWi4b9lHfvEC2poJ2I2vXwGUREVEQTcdUguOpfqdcHM= -go.opentelemetry.io/collector/confmap/xconfmap v0.145.0 h1:ngbyfh4+SKlA+osgsak3AxUNPxVxaJTmA0Sl7VfJzwY= -go.opentelemetry.io/collector/confmap/xconfmap v0.145.0/go.mod h1:zTSK+c76NAy/tI1R3xfZjdoI04D9EYDnzAHQQwl6AmA= go.opentelemetry.io/collector/consumer v1.51.0 h1:Ex1x/k9VEEA2DOgt/eSc2Z9KTp0I6xBSruLmrYFfIFY= go.opentelemetry.io/collector/consumer v1.51.0/go.mod h1:Erk6qdfVj+24QTrGCpurcrF+qdUlHkb4dgMy5wJxLvY= go.opentelemetry.io/collector/consumer/consumererror v0.145.0 h1:UtcJ0mH9D7R9sexzSGOg8VpZ+m2N93owyEnReraB8UQ= @@ -105,16 +75,6 @@ go.opentelemetry.io/collector/consumer/consumertest v0.145.0 h1:3+uMwuMHoXMAU+Z6 go.opentelemetry.io/collector/consumer/consumertest v0.145.0/go.mod h1:IFc/FeaIHQClb8KK0aVn0tFDNMc+/MmfQ+aBT1cJNeo= go.opentelemetry.io/collector/consumer/xconsumer v0.145.0 h1:9w7KKv9lVJoHvMLC6SUJHenU/KySdEgFJXbB4JQOEsk= go.opentelemetry.io/collector/consumer/xconsumer v0.145.0/go.mod h1:SryDCLP2ZaFeZJtA2CSksJ0XvjH8k3LmlfXvy/kC7Wc= -go.opentelemetry.io/collector/extension v1.51.0 h1:NWYhvGRHHK+g1WdHqVdFuKsDtIfYoudfJ0dC6TbIfWE= -go.opentelemetry.io/collector/extension v1.51.0/go.mod h1:y5Z0djLtw0QZb8CJQv8JpeObx9bfAnw3yeu1yoKhyaA= -go.opentelemetry.io/collector/extension/extensionauth v1.51.0 h1:ox3nzKx8a/6Rf2DiuK6qUDIYbXK4frW0INZoPTFY7Xw= -go.opentelemetry.io/collector/extension/extensionauth v1.51.0/go.mod h1:alIyB3zBUOvIEn/DaAdLMFWtz9Zw4UYt1iHO0lMy5XU= -go.opentelemetry.io/collector/extension/extensionauth/extensionauthtest v0.145.0 h1:irVSyUVTp71InKizZhoTe0oDoj4vAvVsYonybfRVfQc= -go.opentelemetry.io/collector/extension/extensionauth/extensionauthtest v0.145.0/go.mod h1:1jshMRyK6EvdJxlCf2aCiRHVlVJPdNM40isETkx3jX4= -go.opentelemetry.io/collector/extension/extensionmiddleware v0.145.0 h1:2pfnfiDEM2iHEhYj0EbkwhKvNJFfTfAx5zWZeO6PyoQ= -go.opentelemetry.io/collector/extension/extensionmiddleware v0.145.0/go.mod h1:CyKahcem/CnsjFSpWXOCWk0OaB7fraO+bSHar3uAsDY= -go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest v0.145.0 h1:Cir87cjIiRjtMxiF833tTxVuZvD3diXyBpsNlouiLB8= -go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest v0.145.0/go.mod h1:xqCp8tnkBYhjuL8WYEaC721cAFWJjPz8yaIIQ8+j5os= go.opentelemetry.io/collector/featuregate v1.51.0 h1:dxJuv/3T84dhNKp7fz5+8srHz1dhquGzDpLW4OZTFBw= go.opentelemetry.io/collector/featuregate v1.51.0/go.mod h1:/1bclXgP91pISaEeNulRxzzmzMTm4I5Xih2SnI4HRSo= go.opentelemetry.io/collector/internal/componentalias v0.145.0 h1:A9V5IiETzz8FCtjxjRM5gf7RE3sOtA1h8phmpQjXTZ4= @@ -135,18 +95,16 @@ go.opentelemetry.io/collector/receiver/receivertest v0.145.0 h1:JlEM4VWvoUMkllUc go.opentelemetry.io/collector/receiver/receivertest v0.145.0/go.mod h1:iitTZ7Z2QTkr9oi3mN0IIMXG9Y6Pn2xTX31Cyyyp4/8= go.opentelemetry.io/collector/receiver/xreceiver v0.145.0 h1:vkWKqPX6g7FWPuZlgxAVk8N+uMg5WGh/bZINdGsIgGY= go.opentelemetry.io/collector/receiver/xreceiver v0.145.0/go.mod h1:HlEYrvW52PWoL92jRRLzlmJ2hwWaKBzaoo6FFDZpHx4= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= -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 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.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= -go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= +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.9.0 h1:fPVMv8tP3TrsqlkH1HWYUpbCY9cAIemx184VGkS6vlE= go.opentelemetry.io/proto/slim/otlp v1.9.0/go.mod h1:xXdeJJ90Gqyll+orzUkY4bOd2HECo5JofeoLpymVqdI= go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.2.0 h1:o13nadWDNkH/quoDomDUClnQBpdQQ2Qqv0lQBjIXjE8= @@ -161,16 +119,12 @@ 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.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= -golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= -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.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= From 98a02ba7ad9f7c3915ba3c9185e13f02991cd134 Mon Sep 17 00:00:00 2001 From: Antoine Toulme Date: Thu, 20 Nov 2025 20:56:25 -0800 Subject: [PATCH 2/3] use errGroup --- receiver/pprofreceiver/factory.go | 67 ++++++++++++++++--------------- receiver/pprofreceiver/go.mod | 1 + receiver/pprofreceiver/go.sum | 2 + 3 files changed, 38 insertions(+), 32 deletions(-) diff --git a/receiver/pprofreceiver/factory.go b/receiver/pprofreceiver/factory.go index de7f49c3d83fc..a9015098d2d8c 100644 --- a/receiver/pprofreceiver/factory.go +++ b/receiver/pprofreceiver/factory.go @@ -18,6 +18,7 @@ import ( "go.opentelemetry.io/collector/receiver" "go.opentelemetry.io/collector/receiver/xreceiver" "go.uber.org/zap" + "golang.org/x/sync/errgroup" pproftranslator "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/pprof" "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/pprofreceiver/internal/metadata" @@ -55,6 +56,7 @@ type pprofReceiver struct { telemetrySettings component.TelemetrySettings config *Config done chan struct{} + errGrp errgroup.Group } func (r *pprofReceiver) convert(p []byte) (pprofile.Profiles, error) { @@ -75,47 +77,48 @@ func (r *pprofReceiver) Start(_ context.Context, _ component.Host) error { runtime.SetBlockProfileRate(r.config.BlockProfileFraction) runtime.SetMutexProfileFraction(r.config.MutexProfileFraction) - go func() { - timer := time.NewTicker(r.config.CollectionInterval) - collecting := false - payload := make([]byte, 0, 8096) - buf := bytes.NewBuffer(payload) - writer := bufio.NewWriter(buf) - for { - select { - case <-r.done: + r.errGrp.Go(r.run) + + return nil +} + +func (r *pprofReceiver) run() error { + timer := time.NewTicker(r.config.CollectionInterval) + collecting := false + buf := bytes.NewBuffer(make([]byte, 0, 8096)) + writer := bufio.NewWriter(buf) + for { + select { + case <-r.done: + pprof.StopCPUProfile() + return nil + case <-timer.C: + if collecting { pprof.StopCPUProfile() - return - case <-timer.C: - if collecting { - pprof.StopCPUProfile() - _ = writer.Flush() - collecting = false - pp, err := r.convert(buf.Bytes()) - buf.Reset() - if err != nil { - r.telemetrySettings.Logger.Error("error processing profile", zap.Error(err)) - } else { - err = r.consumer.ConsumeProfiles(context.Background(), pp) - if err != nil { - r.telemetrySettings.Logger.Error("failed to ingest pprof profile", zap.Error(err)) - } - } + _ = writer.Flush() + collecting = false + pp, err := r.convert(buf.Bytes()) + buf.Reset() + if err != nil { + r.telemetrySettings.Logger.Error("error processing profile", zap.Error(err)) } else { - err := pprof.StartCPUProfile(writer) + err = r.consumer.ConsumeProfiles(context.Background(), pp) if err != nil { - r.telemetrySettings.Logger.Error("failed to start CPU profile", zap.Error(err)) + r.telemetrySettings.Logger.Error("failed to ingest pprof profile", zap.Error(err)) } - collecting = true } + } else { + err := pprof.StartCPUProfile(writer) + if err != nil { + r.telemetrySettings.Logger.Error("failed to start CPU profile", zap.Error(err)) + } + collecting = true } } - }() - - return nil + } } func (r *pprofReceiver) Shutdown(_ context.Context) error { close(r.done) - return nil + return r.errGrp.Wait() } diff --git a/receiver/pprofreceiver/go.mod b/receiver/pprofreceiver/go.mod index fcfa834949ee5..abe117cd5c901 100644 --- a/receiver/pprofreceiver/go.mod +++ b/receiver/pprofreceiver/go.mod @@ -17,6 +17,7 @@ require ( go.opentelemetry.io/collector/receiver/xreceiver v0.145.0 go.uber.org/goleak v1.3.0 go.uber.org/zap v1.27.1 + golang.org/x/sync v0.18.0 ) require ( diff --git a/receiver/pprofreceiver/go.sum b/receiver/pprofreceiver/go.sum index 2022562a3be01..d09073b833442 100644 --- a/receiver/pprofreceiver/go.sum +++ b/receiver/pprofreceiver/go.sum @@ -121,6 +121,8 @@ 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.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= From 41348b099059e94e1a4569c5d7d4c880e24606b5 Mon Sep 17 00:00:00 2001 From: Antoine Toulme Date: Thu, 20 Nov 2025 21:55:20 -0800 Subject: [PATCH 3/3] add back remote endpoint read --- pkg/translator/pprof/pprof_to_profiles.go | 2 +- receiver/pprofreceiver/README.md | 7 +- receiver/pprofreceiver/config.go | 8 +- receiver/pprofreceiver/factory.go | 83 +------------ receiver/pprofreceiver/factory_test.go | 36 ++++++ receiver/pprofreceiver/go.mod | 24 +++- receiver/pprofreceiver/go.sum | 58 ++++++++- receiver/pprofreceiver/receiver.go | 139 ++++++++++++++++++++++ 8 files changed, 270 insertions(+), 87 deletions(-) create mode 100644 receiver/pprofreceiver/receiver.go diff --git a/pkg/translator/pprof/pprof_to_profiles.go b/pkg/translator/pprof/pprof_to_profiles.go index 393b8527bc747..c863adb345254 100644 --- a/pkg/translator/pprof/pprof_to_profiles.go +++ b/pkg/translator/pprof/pprof_to_profiles.go @@ -95,7 +95,7 @@ type lookupTables struct { lastStackTableIdx int32 } -func convertPprofToPprofile(src *profile.Profile) (*pprofile.Profiles, error) { +func ConvertPprofToPprofile(src *profile.Profile) (*pprofile.Profiles, error) { if err := src.CheckValid(); err != nil { return nil, fmt.Errorf("%w: %w", err, errPprofInvalid) } diff --git a/receiver/pprofreceiver/README.md b/receiver/pprofreceiver/README.md index b62608b887a94..8d9dfe404d77d 100644 --- a/receiver/pprofreceiver/README.md +++ b/receiver/pprofreceiver/README.md @@ -24,4 +24,9 @@ receivers: pprof: ``` -In a future iteration, this receiver will support collecting pprof data from a remote pprof endpoint. +Collector with remote endpoint: +```yaml +receivers: + pprof: + endpoint: "http://localhost:6060/debug/pprof/profile?seconds=1" +``` diff --git a/receiver/pprofreceiver/config.go b/receiver/pprofreceiver/config.go index 9958f8fc37a93..046048f64a3be 100644 --- a/receiver/pprofreceiver/config.go +++ b/receiver/pprofreceiver/config.go @@ -3,9 +3,15 @@ package pprofreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/pprofreceiver" -import "time" +import ( + "time" + + "go.opentelemetry.io/collector/config/confighttp" +) type Config struct { + confighttp.ClientConfig `mapstructure:",squash"` + // CollectionInterval sets how frequently profiles should be collected. CollectionInterval time.Duration `mapstructure:"collection_interval"` diff --git a/receiver/pprofreceiver/factory.go b/receiver/pprofreceiver/factory.go index a9015098d2d8c..4031b900e2eb6 100644 --- a/receiver/pprofreceiver/factory.go +++ b/receiver/pprofreceiver/factory.go @@ -4,23 +4,15 @@ package pprofreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/pprofreceiver" import ( - "bufio" - "bytes" "context" - "runtime" - "runtime/pprof" "time" - "github.com/google/pprof/profile" "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/confighttp" "go.opentelemetry.io/collector/consumer/xconsumer" - "go.opentelemetry.io/collector/pdata/pprofile" "go.opentelemetry.io/collector/receiver" "go.opentelemetry.io/collector/receiver/xreceiver" - "go.uber.org/zap" - "golang.org/x/sync/errgroup" - pproftranslator "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/pprof" "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/pprofreceiver/internal/metadata" ) @@ -33,6 +25,7 @@ func NewFactory() receiver.Factory { func createDefaultConfig() component.Config { return &Config{ + ClientConfig: confighttp.NewDefaultClientConfig(), CollectionInterval: 10 * time.Second, } } @@ -50,75 +43,3 @@ func createProfilesReceiver( done: make(chan struct{}), }, nil } - -type pprofReceiver struct { - consumer xconsumer.Profiles - telemetrySettings component.TelemetrySettings - config *Config - done chan struct{} - errGrp errgroup.Group -} - -func (r *pprofReceiver) convert(p []byte) (pprofile.Profiles, error) { - parsed, err := profile.ParseData(p) - if err != nil { - r.telemetrySettings.Logger.Debug("failed to parse pprof profile", zap.Error(err)) - return pprofile.Profiles{}, err - } - pp, err := pproftranslator.ConvertPprofToPprofile(parsed) - if err != nil { - r.telemetrySettings.Logger.Debug("failed to convert pprof profile", zap.Error(err)) - return pprofile.Profiles{}, err - } - return *pp, nil -} - -func (r *pprofReceiver) Start(_ context.Context, _ component.Host) error { - runtime.SetBlockProfileRate(r.config.BlockProfileFraction) - runtime.SetMutexProfileFraction(r.config.MutexProfileFraction) - - r.errGrp.Go(r.run) - - return nil -} - -func (r *pprofReceiver) run() error { - timer := time.NewTicker(r.config.CollectionInterval) - collecting := false - buf := bytes.NewBuffer(make([]byte, 0, 8096)) - writer := bufio.NewWriter(buf) - for { - select { - case <-r.done: - pprof.StopCPUProfile() - return nil - case <-timer.C: - if collecting { - pprof.StopCPUProfile() - _ = writer.Flush() - collecting = false - pp, err := r.convert(buf.Bytes()) - buf.Reset() - if err != nil { - r.telemetrySettings.Logger.Error("error processing profile", zap.Error(err)) - } else { - err = r.consumer.ConsumeProfiles(context.Background(), pp) - if err != nil { - r.telemetrySettings.Logger.Error("failed to ingest pprof profile", zap.Error(err)) - } - } - } else { - err := pprof.StartCPUProfile(writer) - if err != nil { - r.telemetrySettings.Logger.Error("failed to start CPU profile", zap.Error(err)) - } - collecting = true - } - } - } -} - -func (r *pprofReceiver) Shutdown(_ context.Context) error { - close(r.done) - return r.errGrp.Wait() -} diff --git a/receiver/pprofreceiver/factory_test.go b/receiver/pprofreceiver/factory_test.go index 5602931efd2e0..87d2ca8645393 100644 --- a/receiver/pprofreceiver/factory_test.go +++ b/receiver/pprofreceiver/factory_test.go @@ -3,6 +3,8 @@ package pprofreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/pprofreceiver" import ( + "net/http" + _ "net/http/pprof" //nolint:gosec // #nosec G108 "testing" "time" @@ -34,3 +36,37 @@ func TestStartStop(t *testing.T) { err = r.Shutdown(t.Context()) require.NoError(t, err) } + +func TestStartStopRemote(t *testing.T) { + server := &http.Server{ + ReadTimeout: 1 * time.Second, + Addr: "localhost:6060", + Handler: http.DefaultServeMux, + } + go func() { + err := server.ListenAndServe() + assert.ErrorIs(t, err, http.ErrServerClosed, "server closed") + }() + defer func() { + assert.NoError(t, server.Shutdown(t.Context())) + }() + + f := NewFactory().(xreceiver.Factory) + profilesConsumer := new(consumertest.ProfilesSink) + set := receivertest.NewNopSettings(f.Type()) + var err error + set.Logger, err = zap.NewDevelopment() + require.NoError(t, err) + cfg := f.CreateDefaultConfig().(*Config) + cfg.CollectionInterval = 1 * time.Second + cfg.Endpoint = "http://localhost:6060/debug/pprof/profile?seconds=1" + r, err := f.CreateProfiles(t.Context(), set, cfg, profilesConsumer) + require.NoError(t, err) + err = r.Start(t.Context(), componenttest.NewNopHost()) + require.NoError(t, err) + require.EventuallyWithT(t, func(tt *assert.CollectT) { + require.NotEmpty(tt, profilesConsumer.AllProfiles()) + }, 5*time.Second, 100*time.Millisecond, "failed to receive data from pprof receiver") + err = r.Shutdown(t.Context()) + require.NoError(t, err) +} diff --git a/receiver/pprofreceiver/go.mod b/receiver/pprofreceiver/go.mod index abe117cd5c901..d3efd28e7e340 100644 --- a/receiver/pprofreceiver/go.mod +++ b/receiver/pprofreceiver/go.mod @@ -8,6 +8,7 @@ require ( github.com/stretchr/testify v1.11.1 go.opentelemetry.io/collector/component v1.51.0 go.opentelemetry.io/collector/component/componenttest v0.145.0 + go.opentelemetry.io/collector/config/confighttp v0.140.0 go.opentelemetry.io/collector/confmap v1.51.0 go.opentelemetry.io/collector/consumer/consumertest v0.145.0 go.opentelemetry.io/collector/consumer/xconsumer v0.145.0 @@ -17,19 +18,25 @@ require ( go.opentelemetry.io/collector/receiver/xreceiver v0.145.0 go.uber.org/goleak v1.3.0 go.uber.org/zap v1.27.1 - golang.org/x/sync v0.18.0 + golang.org/x/sync v0.19.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-20250903184740-5d135037bd4d // 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.7 // 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.1 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/knadh/koanf/maps v0.1.2 // indirect github.com/knadh/koanf/providers/confmap v1.0.0 // indirect @@ -38,15 +45,28 @@ require ( 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.22 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rs/cors v1.11.1 // indirect github.com/zeebo/xxh3 v1.1.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/collector/client v1.46.0 // indirect + go.opentelemetry.io/collector/config/configauth v1.46.0 // indirect + go.opentelemetry.io/collector/config/configcompression v1.46.0 // indirect + go.opentelemetry.io/collector/config/configmiddleware v1.46.0 // indirect + go.opentelemetry.io/collector/config/configopaque v1.46.0 // indirect + go.opentelemetry.io/collector/config/configoptional v1.46.0 // indirect + go.opentelemetry.io/collector/config/configtls v1.46.0 // indirect + go.opentelemetry.io/collector/confmap/xconfmap v0.140.0 // indirect go.opentelemetry.io/collector/consumer v1.51.0 // indirect go.opentelemetry.io/collector/consumer/consumererror v0.145.0 // indirect + go.opentelemetry.io/collector/extension/extensionauth v1.46.0 // indirect + go.opentelemetry.io/collector/extension/extensionmiddleware v0.140.0 // indirect go.opentelemetry.io/collector/featuregate v1.51.0 // indirect go.opentelemetry.io/collector/internal/componentalias v0.145.0 // indirect go.opentelemetry.io/collector/pdata v1.51.0 // indirect go.opentelemetry.io/collector/pipeline v1.51.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect go.opentelemetry.io/otel v1.40.0 // indirect go.opentelemetry.io/otel/metric v1.40.0 // indirect go.opentelemetry.io/otel/sdk v1.39.0 // indirect @@ -54,8 +74,10 @@ require ( go.opentelemetry.io/otel/trace v1.40.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/crypto v0.47.0 // indirect golang.org/x/net v0.49.0 // indirect golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.33.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect google.golang.org/grpc v1.78.0 // indirect google.golang.org/protobuf v1.36.11 // indirect diff --git a/receiver/pprofreceiver/go.sum b/receiver/pprofreceiver/go.sum index d09073b833442..10d30ebee9bd2 100644 --- a/receiver/pprofreceiver/go.sum +++ b/receiver/pprofreceiver/go.sum @@ -3,6 +3,14 @@ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL 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-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-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= @@ -14,8 +22,14 @@ 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.7 h1:u89J4tUUeDTlH8xxC3CTW7OHZjbjKoHdQ9W7gCUhtxA= +github.com/google/go-tpm v0.9.7/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= +github.com/google/go-tpm-tools v0.4.4 h1:oiQfAIkc6xTy9Fl5NKTeTJkBTlXdHsxAofmQyxBKY98= +github.com/google/go-tpm-tools v0.4.4/go.mod h1:T8jXkp2s+eltnCDIsXR84/MTcVU9Ja7bh3Mit0pa4AY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8 h1:3DsUAV+VNEQa2CUVLxCY3f87278uWfIDhJnbdvDjvmE= github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U= @@ -25,6 +39,8 @@ github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bP 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.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co= +github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0= github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo= @@ -47,10 +63,14 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ 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.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= +github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 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= @@ -61,12 +81,30 @@ github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= 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/collector/client v1.46.0 h1:nAEVyKIECez8P92RXa78mjRvaynkivYdukT07lzF7Gs= +go.opentelemetry.io/collector/client v1.46.0/go.mod h1:/Y2bm0RdD8LKIEQOX5YqqjglKNb8AYCdDuKb04/fURw= go.opentelemetry.io/collector/component v1.51.0 h1:btNW76MCRmpsk0ARRT5wspDXF9tvdaLd3uBtYXIiQn0= go.opentelemetry.io/collector/component v1.51.0/go.mod h1:Zlgwh4yTLDhJglOXqiyXZ7paepTvvoijfFjLqOr/Qww= go.opentelemetry.io/collector/component/componenttest v0.145.0 h1:ryhRrXqQybGMhz7A7t32NC8BXAFcX2o1RetgPM7vw88= go.opentelemetry.io/collector/component/componenttest v0.145.0/go.mod h1:5uStrhUdZ0Fw3se00CPmVaRtW8o9N8kKiY76OSCWFjQ= +go.opentelemetry.io/collector/config/configauth v1.46.0 h1:Aq90doQ7QuiqyiJxTX5Li0j/IwSPh2ioeKpPUwXbscM= +go.opentelemetry.io/collector/config/configauth v1.46.0/go.mod h1:Qe6QY+fwv8rZ5PnTSmfzwOHrtI5FxwH6IT5bMw7UibM= +go.opentelemetry.io/collector/config/configcompression v1.46.0 h1:ay0mghHaYrhmG/vbGthuiCbicA/qACa6ET/5dZWn20Q= +go.opentelemetry.io/collector/config/configcompression v1.46.0/go.mod h1:ZlnKaXFYL3HVMUNWVAo/YOLYoxNZo7h8SrQp3l7GV00= +go.opentelemetry.io/collector/config/confighttp v0.140.0 h1:iCk+ROLrKCd0+k8uQSMN5MkDndL9Ob//jPZUaJpmXo0= +go.opentelemetry.io/collector/config/confighttp v0.140.0/go.mod h1:GWZ/czyKbmKZn38p0R+bbPbtlaUQSByrsUbLZpLS87I= +go.opentelemetry.io/collector/config/configmiddleware v1.46.0 h1:w5tFoDLwcDg90itp52NzUCwrBk+dAIT5b01ci36i914= +go.opentelemetry.io/collector/config/configmiddleware v1.46.0/go.mod h1:+JO/m4qRUd8QPiowkQkeYK+1mKnBJaEH+wm0Qbwe5eU= +go.opentelemetry.io/collector/config/configopaque v1.46.0 h1:lEh2VMyxOKJHa02Sj+O5INWTJZygYN2GKa5spWMGQQI= +go.opentelemetry.io/collector/config/configopaque v1.46.0/go.mod h1:OPmPZMkuks+mxK5Mtb0s20o0++BIBPq9oTEh2l4yPqk= +go.opentelemetry.io/collector/config/configoptional v1.46.0 h1:BZnFi2NUSEeP2ttr7bwGdo6a8UDcYEkfrq7SiP1jjac= +go.opentelemetry.io/collector/config/configoptional v1.46.0/go.mod h1:XgGvHiFtro2MpPWbo4ExQ7CLnSBqzWAANfBIPv4QSVg= +go.opentelemetry.io/collector/config/configtls v1.46.0 h1:vrUtOTOpS+oOne/8NpOYKZnOHHrK9GKCevwyoqjQNVs= +go.opentelemetry.io/collector/config/configtls v1.46.0/go.mod h1:WQcQCiltzLTkLB9VdckHnied7HeEPTNCnobMl+JFfYY= go.opentelemetry.io/collector/confmap v1.51.0 h1:C9YlMNkIgzuauLpUz2F7DLlWwqAmkQKNcKj1XATVWuE= go.opentelemetry.io/collector/confmap v1.51.0/go.mod h1:uWi4b9lHfvEC2poJ2I2vXwGUREVEQTcdUguOpfqdcHM= +go.opentelemetry.io/collector/confmap/xconfmap v0.140.0 h1:rTHo7f3d4h00qCpb4hYnu/+n48sd5Hd4E9KT47QTgZA= +go.opentelemetry.io/collector/confmap/xconfmap v0.140.0/go.mod h1:KInqGVGClR7dDDJLkHsl3riO03et7TaBrGKVD5pD4i0= go.opentelemetry.io/collector/consumer v1.51.0 h1:Ex1x/k9VEEA2DOgt/eSc2Z9KTp0I6xBSruLmrYFfIFY= go.opentelemetry.io/collector/consumer v1.51.0/go.mod h1:Erk6qdfVj+24QTrGCpurcrF+qdUlHkb4dgMy5wJxLvY= go.opentelemetry.io/collector/consumer/consumererror v0.145.0 h1:UtcJ0mH9D7R9sexzSGOg8VpZ+m2N93owyEnReraB8UQ= @@ -75,6 +113,16 @@ go.opentelemetry.io/collector/consumer/consumertest v0.145.0 h1:3+uMwuMHoXMAU+Z6 go.opentelemetry.io/collector/consumer/consumertest v0.145.0/go.mod h1:IFc/FeaIHQClb8KK0aVn0tFDNMc+/MmfQ+aBT1cJNeo= go.opentelemetry.io/collector/consumer/xconsumer v0.145.0 h1:9w7KKv9lVJoHvMLC6SUJHenU/KySdEgFJXbB4JQOEsk= go.opentelemetry.io/collector/consumer/xconsumer v0.145.0/go.mod h1:SryDCLP2ZaFeZJtA2CSksJ0XvjH8k3LmlfXvy/kC7Wc= +go.opentelemetry.io/collector/extension v1.46.0 h1:+ATT9ADkMUR0cRH8J53vU9MRJ9UspRC0B+BqDGW1aRE= +go.opentelemetry.io/collector/extension v1.46.0/go.mod h1:/NGiZQFF7hTyfRULTgtYw27cIW8i0hWUTp12lDftZS0= +go.opentelemetry.io/collector/extension/extensionauth v1.46.0 h1:JvGu9tp+PIPgvXUSSyKMqShtK44ooK6+FAtpBnvaPPc= +go.opentelemetry.io/collector/extension/extensionauth v1.46.0/go.mod h1:6Sh0hqPfPqpg0ErCoNPO/ky2NdfGmUX+G5wekPx7A7U= +go.opentelemetry.io/collector/extension/extensionauth/extensionauthtest v0.140.0 h1:ulNNHU2KJ0RqCIgNl9rMVaVhr25nQhJoF/2iL1G4ZGk= +go.opentelemetry.io/collector/extension/extensionauth/extensionauthtest v0.140.0/go.mod h1:YKsJ4qSu+aX3LyM27GF/A5JsnkjgRrRnduGGw8G7Ov4= +go.opentelemetry.io/collector/extension/extensionmiddleware v0.140.0 h1:L2xKxXWErYvir4k/yaGmz+NDCe7PGBM5ZNjbsOanYRI= +go.opentelemetry.io/collector/extension/extensionmiddleware v0.140.0/go.mod h1:/ub63cgY3YraiJJ3pBuxDnxEzeEXqniuRDQYf6NIBDE= +go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest v0.140.0 h1:qDvDgU+nZrONS/Z2aS3HH8p12bYNzUxKM6eaX1XD7d8= +go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest v0.140.0/go.mod h1:LZvOvHxC9zLkN9kCDMCn0uQrYYR3g3NwPvGTfr4es5k= go.opentelemetry.io/collector/featuregate v1.51.0 h1:dxJuv/3T84dhNKp7fz5+8srHz1dhquGzDpLW4OZTFBw= go.opentelemetry.io/collector/featuregate v1.51.0/go.mod h1:/1bclXgP91pISaEeNulRxzzmzMTm4I5Xih2SnI4HRSo= go.opentelemetry.io/collector/internal/componentalias v0.145.0 h1:A9V5IiETzz8FCtjxjRM5gf7RE3sOtA1h8phmpQjXTZ4= @@ -95,6 +143,8 @@ go.opentelemetry.io/collector/receiver/receivertest v0.145.0 h1:JlEM4VWvoUMkllUc go.opentelemetry.io/collector/receiver/receivertest v0.145.0/go.mod h1:iitTZ7Z2QTkr9oi3mN0IIMXG9Y6Pn2xTX31Cyyyp4/8= go.opentelemetry.io/collector/receiver/xreceiver v0.145.0 h1:vkWKqPX6g7FWPuZlgxAVk8N+uMg5WGh/bZINdGsIgGY= go.opentelemetry.io/collector/receiver/xreceiver v0.145.0/go.mod h1:HlEYrvW52PWoL92jRRLzlmJ2hwWaKBzaoo6FFDZpHx4= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= 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= @@ -119,14 +169,18 @@ 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.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +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.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +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.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= diff --git a/receiver/pprofreceiver/receiver.go b/receiver/pprofreceiver/receiver.go new file mode 100644 index 0000000000000..2b6cf0d7533f3 --- /dev/null +++ b/receiver/pprofreceiver/receiver.go @@ -0,0 +1,139 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package pprofreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/pprofreceiver" +import ( + "bufio" + "bytes" + "context" + "io" + "net/http" + "runtime" + "runtime/pprof" + "time" + + "github.com/google/pprof/profile" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/consumer/xconsumer" + "go.opentelemetry.io/collector/pdata/pprofile" + "go.uber.org/zap" + "golang.org/x/sync/errgroup" + + pproftranslator "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/pprof" +) + +type pprofReceiver struct { + consumer xconsumer.Profiles + telemetrySettings component.TelemetrySettings + config *Config + done chan struct{} + errGrp errgroup.Group + client *http.Client +} + +func (r *pprofReceiver) convert(p []byte) (pprofile.Profiles, error) { + parsed, err := profile.ParseData(p) + if err != nil { + r.telemetrySettings.Logger.Debug("failed to parse pprof profile", zap.Error(err)) + return pprofile.Profiles{}, err + } + pp, err := pproftranslator.ConvertPprofToPprofile(parsed) + if err != nil { + r.telemetrySettings.Logger.Debug("failed to convert pprof profile", zap.Error(err)) + return pprofile.Profiles{}, err + } + return *pp, nil +} + +func (r *pprofReceiver) Start(ctx context.Context, host component.Host) error { + runtime.SetBlockProfileRate(r.config.BlockProfileFraction) + runtime.SetMutexProfileFraction(r.config.MutexProfileFraction) + + if r.config.Endpoint != "" { + var err error + r.client, err = r.config.ToClient(ctx, host, r.telemetrySettings) + if err != nil { + return err + } + r.errGrp.Go(r.runRemote) + } else { + r.errGrp.Go(r.run) + } + + return nil +} + +func (r *pprofReceiver) runRemote() error { + timer := time.NewTicker(r.config.CollectionInterval) + for { + select { + case <-r.done: + return nil + case <-timer.C: + resp, err := r.client.Get(r.config.Endpoint) + if err != nil { + r.telemetrySettings.Logger.Error("error requesting profile", zap.Error(err), zap.String("endpoint", r.config.Endpoint)) + continue + } + body, err := io.ReadAll(resp.Body) + if err != nil { + r.telemetrySettings.Logger.Error("failed to read HTTP response", zap.Error(err)) + continue + } + _ = resp.Body.Close() + pp, err := r.convert(body) + if err != nil { + r.telemetrySettings.Logger.Error("failed to convert pprof profile", zap.Error(err)) + continue + } + err = r.consumer.ConsumeProfiles(context.Background(), pp) + if err != nil { + r.telemetrySettings.Logger.Error("failed to ingest pprof profile", zap.Error(err)) + } + } + } +} + +func (r *pprofReceiver) run() error { + timer := time.NewTicker(r.config.CollectionInterval) + collecting := false + buf := bytes.NewBuffer(make([]byte, 0, 8096)) + writer := bufio.NewWriter(buf) + for { + select { + case <-r.done: + pprof.StopCPUProfile() + return nil + case <-timer.C: + if collecting { + pprof.StopCPUProfile() + _ = writer.Flush() + collecting = false + pp, err := r.convert(buf.Bytes()) + buf.Reset() + if err != nil { + r.telemetrySettings.Logger.Error("error processing profile", zap.Error(err)) + } else { + err = r.consumer.ConsumeProfiles(context.Background(), pp) + if err != nil { + r.telemetrySettings.Logger.Error("failed to ingest pprof profile", zap.Error(err)) + } + } + } else { + err := pprof.StartCPUProfile(writer) + if err != nil { + r.telemetrySettings.Logger.Error("failed to start CPU profile", zap.Error(err)) + } + collecting = true + } + } + } +} + +func (r *pprofReceiver) Shutdown(_ context.Context) error { + close(r.done) + if r.client != nil { + r.client.CloseIdleConnections() + } + return r.errGrp.Wait() +}