Skip to content

Commit 96f35cf

Browse files
committed
[scraper] add Profiles support
Signed-off-by: Florian Lehner <[email protected]>
1 parent 6ccdc89 commit 96f35cf

File tree

6 files changed

+199
-8
lines changed

6 files changed

+199
-8
lines changed

.chloggen/scraper-profiles.yaml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type: 'enhancement'
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. otlpreceiver)
7+
component: scraper
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Implement scraper for Profiles.
11+
12+
# One or more tracking issues or pull requests related to the change
13+
issues: []
14+
15+
# (Optional) One or more lines of additional information to render under the primary note.
16+
# These lines will be padded with 2 spaces and then inserted directly into the document.
17+
# Use pipe (|) for multiline entries.
18+
subtext:
19+
20+
# Optional: The change log or logs in which this entry should be included.
21+
# e.g. '[user]' or '[user, api]'
22+
# Include 'user' if the change is relevant to end users.
23+
# Include 'api' if there is a change to a library API.
24+
# Default: '[user]'
25+
change_logs: [user]

scraper/factory.go

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,20 @@ type Factory interface {
4141
// this function returns the error [pipeline.ErrSignalNotSupported].
4242
CreateMetrics(ctx context.Context, set Settings, cfg component.Config) (Metrics, error)
4343

44+
// CreateProfiles creates a Profiles scraper based on this config.
45+
// If the scraper type does not support profiles,
46+
// this function returns the error [pipeline.ErrSignalNotSupported].
47+
CreateProfiles(ctx context.Context, set Settings, cfg component.Config) (Profiles, error)
48+
4449
// LogsStability gets the stability level of the Logs scraper.
4550
LogsStability() component.StabilityLevel
4651

4752
// MetricsStability gets the stability level of the Metrics scraper.
4853
MetricsStability() component.StabilityLevel
4954

55+
// ProfilesStability gets the stability level of the Profiles scraper.
56+
ProfilesStability() component.StabilityLevel
57+
5058
unexportedFactoryFunc()
5159
}
5260

@@ -68,10 +76,12 @@ func (f factoryOptionFunc) applyOption(o *factory) {
6876
type factory struct {
6977
cfgType component.Type
7078
component.CreateDefaultConfigFunc
71-
createLogsFunc CreateLogsFunc
72-
createMetricsFunc CreateMetricsFunc
73-
logsStabilityLevel component.StabilityLevel
74-
metricsStabilityLevel component.StabilityLevel
79+
createLogsFunc CreateLogsFunc
80+
createMetricsFunc CreateMetricsFunc
81+
createProfilesFunc CreateProfilesFunc
82+
logsStabilityLevel component.StabilityLevel
83+
metricsStabilityLevel component.StabilityLevel
84+
profilesStabilityLevel component.StabilityLevel
7585
}
7686

7787
func (f *factory) Type() component.Type {
@@ -88,6 +98,10 @@ func (f *factory) MetricsStability() component.StabilityLevel {
8898
return f.metricsStabilityLevel
8999
}
90100

101+
func (f *factory) ProfilesStability() component.StabilityLevel {
102+
return f.profilesStabilityLevel
103+
}
104+
91105
func (f *factory) CreateLogs(ctx context.Context, set Settings, cfg component.Config) (Logs, error) {
92106
if f.createLogsFunc == nil {
93107
return nil, pipeline.ErrSignalNotSupported
@@ -102,12 +116,22 @@ func (f *factory) CreateMetrics(ctx context.Context, set Settings, cfg component
102116
return f.createMetricsFunc(ctx, set, cfg)
103117
}
104118

119+
func (f *factory) CreateProfiles(ctx context.Context, set Settings, cfg component.Config) (Profiles, error) {
120+
if f.createProfilesFunc == nil {
121+
return nil, pipeline.ErrSignalNotSupported
122+
}
123+
return f.createProfilesFunc(ctx, set, cfg)
124+
}
125+
105126
// CreateLogsFunc is the equivalent of Factory.CreateLogs().
106127
type CreateLogsFunc func(context.Context, Settings, component.Config) (Logs, error)
107128

108129
// CreateMetricsFunc is the equivalent of Factory.CreateMetrics().
109130
type CreateMetricsFunc func(context.Context, Settings, component.Config) (Metrics, error)
110131

132+
// CreateProfilesFunc is the equivalent of Factory.CreateProfiles().
133+
type CreateProfilesFunc func(context.Context, Settings, component.Config) (Profiles, error)
134+
111135
// WithLogs overrides the default "error not supported" implementation for CreateLogs and the default "undefined" stability level.
112136
func WithLogs(createLogs CreateLogsFunc, sl component.StabilityLevel) FactoryOption {
113137
return factoryOptionFunc(func(o *factory) {
@@ -124,6 +148,14 @@ func WithMetrics(createMetrics CreateMetricsFunc, sl component.StabilityLevel) F
124148
})
125149
}
126150

151+
// WithProfiles overrides the default "error not supported" implementation for CreateProfiles and the default "undefined" stability level.
152+
func WithProfiles(createProfiles CreateProfilesFunc, sl component.StabilityLevel) FactoryOption {
153+
return factoryOptionFunc(func(o *factory) {
154+
o.profilesStabilityLevel = sl
155+
o.createProfilesFunc = createProfiles
156+
})
157+
}
158+
127159
// NewFactory returns a Factory.
128160
func NewFactory(cfgType component.Type, createDefaultConfig component.CreateDefaultConfigFunc, options ...FactoryOption) Factory {
129161
f := &factory{

scraper/go.mod

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ require (
77
go.opentelemetry.io/collector/component v1.42.0
88
go.opentelemetry.io/collector/component/componenttest v0.136.0
99
go.opentelemetry.io/collector/pdata v1.42.0
10+
go.opentelemetry.io/collector/pdata/pprofile v0.136.1-0.20250926084501-6ccdc890b16f
1011
go.opentelemetry.io/collector/pipeline v1.42.0
1112
go.uber.org/goleak v1.3.0
1213
go.uber.org/multierr v1.11.0
@@ -34,10 +35,10 @@ require (
3435
go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect
3536
go.opentelemetry.io/otel/trace v1.38.0 // indirect
3637
go.uber.org/zap v1.27.0 // indirect
37-
golang.org/x/net v0.41.0 // indirect
38-
golang.org/x/sys v0.35.0 // indirect
39-
golang.org/x/text v0.26.0 // indirect
40-
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect
38+
golang.org/x/net v0.44.0 // indirect
39+
golang.org/x/sys v0.36.0 // indirect
40+
golang.org/x/text v0.29.0 // indirect
41+
google.golang.org/genproto/googleapis/rpc v0.0.0-20250922171735-9219d122eba9 // indirect
4142
google.golang.org/grpc v1.75.1 // indirect
4243
google.golang.org/protobuf v1.36.9 // indirect
4344
gopkg.in/yaml.v3 v3.0.1 // indirect

scraper/go.sum

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

scraper/profiles.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package scraper // import "go.opentelemetry.io/collector/scraper"
5+
6+
import (
7+
"context"
8+
9+
"go.opentelemetry.io/collector/component"
10+
"go.opentelemetry.io/collector/pdata/pprofile"
11+
)
12+
13+
// Profiles is the base interface for profiles scrapers.
14+
type Profiles interface {
15+
component.Component
16+
17+
// ScrapeProfiles is the base interface to indicate that how should profiles be scraped.
18+
ScrapeProfiles(context.Context) (pprofile.Profiles, error)
19+
}
20+
21+
// ScrapeProfilesFunc is a helper function.
22+
type ScrapeProfilesFunc ScrapeFunc[pprofile.Profiles]
23+
24+
func (sf ScrapeProfilesFunc) ScrapeProfiles(ctx context.Context) (pprofile.Profiles, error) {
25+
return sf(ctx)
26+
}
27+
28+
type profiles struct {
29+
baseScraper
30+
ScrapeProfilesFunc
31+
}
32+
33+
// NewProfiles creates a new Profiles scraper.
34+
func NewProfiles(scrape ScrapeProfilesFunc, options ...Option) (Profiles, error) {
35+
if scrape == nil {
36+
return nil, errNilFunc
37+
}
38+
bs := &profiles{
39+
baseScraper: newBaseScraper(options),
40+
ScrapeProfilesFunc: scrape,
41+
}
42+
return bs, nil
43+
}

scraper/profiles_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package scraper
5+
6+
import (
7+
"context"
8+
"errors"
9+
"sync"
10+
"testing"
11+
12+
"github.com/stretchr/testify/assert"
13+
"github.com/stretchr/testify/require"
14+
"go.opentelemetry.io/collector/component"
15+
"go.opentelemetry.io/collector/component/componenttest"
16+
"go.opentelemetry.io/collector/pdata/pprofile"
17+
)
18+
19+
func TestNewProfiles(t *testing.T) {
20+
mp, err := NewProfiles(newTestScrapeProfilesFunc(nil))
21+
require.NoError(t, err)
22+
23+
require.NoError(t, mp.Start(context.Background(), componenttest.NewNopHost()))
24+
md, err := mp.ScrapeProfiles(context.Background())
25+
require.NoError(t, err)
26+
assert.Equal(t, pprofile.NewProfiles(), md)
27+
require.NoError(t, mp.Shutdown(context.Background()))
28+
}
29+
30+
func TestNewProfiles_WithOptions(t *testing.T) {
31+
want := errors.New("my_error")
32+
mp, err := NewProfiles(newTestScrapeProfilesFunc(nil),
33+
WithStart(func(context.Context, component.Host) error { return want }),
34+
WithShutdown(func(context.Context) error { return want }))
35+
require.NoError(t, err)
36+
37+
assert.Equal(t, want, mp.Start(context.Background(), componenttest.NewNopHost()))
38+
assert.Equal(t, want, mp.Shutdown(context.Background()))
39+
}
40+
41+
func TestNewProfiles_NilRequiredFields(t *testing.T) {
42+
_, err := NewProfiles(nil)
43+
require.Error(t, err)
44+
}
45+
46+
func TestNewProfiles_ProcessProfilesError(t *testing.T) {
47+
want := errors.New("my_error")
48+
mp, err := NewProfiles(newTestScrapeProfilesFunc(want))
49+
require.NoError(t, err)
50+
_, err = mp.ScrapeProfiles(context.Background())
51+
require.ErrorIs(t, err, want)
52+
}
53+
54+
func TestProfilesConcurrency(t *testing.T) {
55+
mp, err := NewProfiles(newTestScrapeProfilesFunc(nil))
56+
require.NoError(t, err)
57+
require.NoError(t, mp.Start(context.Background(), componenttest.NewNopHost()))
58+
59+
var wg sync.WaitGroup
60+
for i := 0; i < 10; i++ {
61+
wg.Add(1)
62+
go func() {
63+
defer wg.Done()
64+
for j := 0; j < 10000; j++ {
65+
_, errScrape := mp.ScrapeProfiles(context.Background())
66+
assert.NoError(t, errScrape)
67+
}
68+
}()
69+
}
70+
wg.Wait()
71+
require.NoError(t, mp.Shutdown(context.Background()))
72+
}
73+
74+
func newTestScrapeProfilesFunc(retError error) ScrapeProfilesFunc {
75+
return func(_ context.Context) (pprofile.Profiles, error) {
76+
return pprofile.NewProfiles(), retError
77+
}
78+
}

0 commit comments

Comments
 (0)