Skip to content

Commit d3d1efb

Browse files
authored
[receiver/pprof] set instrumentation scope name (#49353)
<!--Ex. Fixing a bug - Describe the bug and how this fixes the issue. Ex. Adding a feature - Explain what this achieves.--> #### Description The `receiver/pprof` component builds ScopeProfiles, but it's apparently leaving the `scope name` empty. So when profiles flow out of this receiver, there's no producer identity information and makes it harder to determine the source of translated profile data. fix was simple adding the `sp.Scope().SetName(scopeName)` to the `/pkg/translator/pprof/pprof_to_profiles.go`. Same as done in other translator packages like `translator/azure`. <!-- Issue number (e.g. #1234) or full URL to issue, if applicable. --> #### Link to tracking issue Fixes Fixes #49209 <!--Describe what testing was performed and which tests were added.--> #### Testing Added assertions verifying that converted profiles now include the expected instrumentation scope name (`github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/pprof`). Existing profile conversion tests were updated to validate the scope metadata in addition to profile content. <!--Describe the documentation added.--> #### Documentation No documentation changes required <!--Authorship attestation. See AGENTS.md for details. AI agents must not check this box on behalf of the user; the human author must check it themselves before the PR is ready for review.--> #### Authorship - [x] I, a human, wrote this pull request description myself. <!--Please delete paragraphs that you did not use before submitting.-->
1 parent 1c40208 commit d3d1efb

11 files changed

Lines changed: 150 additions & 5 deletions

File tree

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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. receiver/filelog)
7+
component: receiver/pprof
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Set instrumentation scope name and version on pprof profiles based on the scraper mode (file, http client, http server, self).
11+
12+
# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
13+
issues: [49209]
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: Each scraper now sets the instrumentation scope name using the receiver's ScopeName and a per-mode suffix, and the scope version from BuildInfo.The translator function is unchanged.
19+
20+
# If your change doesn't affect end users or the exported elements of any package,
21+
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
22+
# Optional: The change log or logs in which this entry should be included.
23+
# e.g. '[user]' or '[user, api]'
24+
# Include 'user' if the change is relevant to end users.
25+
# Include 'api' if there is a change to a library API.
26+
# Default: '[user]'
27+
change_logs: [user]

pkg/translator/pprof/pprof_to_profiles.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,6 @@ func ConvertPprofToProfiles(src *profile.Profile) (*pprofile.Profiles, error) {
127127

128128
sp := rp.ScopeProfiles().AppendEmpty()
129129
sp.SetSchemaUrl(semconv.SchemaURL)
130-
131130
// Use a dedicated pprofile.Profile for each sample type.
132131
// By convention, pprof uses the last sample type as default, while OTel Profiles
133132
// uses the first profile as default. Therefore, swap first and last.

receiver/pprofreceiver/internal/file_scraper.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,20 @@ import (
1010

1111
"github.com/bmatcuk/doublestar/v4"
1212
"github.com/google/pprof/profile"
13+
"go.opentelemetry.io/collector/component"
1314
"go.opentelemetry.io/collector/pdata/pprofile"
1415
"go.opentelemetry.io/collector/scraper/scrapererror"
1516
"go.uber.org/multierr"
1617
"go.uber.org/zap"
1718

1819
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/pprof"
20+
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/pprofreceiver/internal/metadata"
1921
)
2022

2123
type FileScraper struct {
22-
Include string
23-
Logger *zap.Logger
24+
Include string
25+
Logger *zap.Logger
26+
BuildInfo component.BuildInfo
2427
}
2528

2629
func (fs FileScraper) Scrape(_ context.Context) (pprofile.Profiles, error) {
@@ -52,6 +55,16 @@ func (fs FileScraper) Scrape(_ context.Context) (pprofile.Profiles, error) {
5255
continue
5356
}
5457

58+
// set ScopeName and Version
59+
for i := 0; i < profiles.ResourceProfiles().Len(); i++ {
60+
rp := profiles.ResourceProfiles().At(i)
61+
for j := 0; j < rp.ScopeProfiles().Len(); j++ {
62+
sp := rp.ScopeProfiles().At(j)
63+
sp.Scope().SetName(metadata.ScopeName + "/filescraper")
64+
sp.Scope().SetVersion(fs.BuildInfo.Version)
65+
}
66+
}
67+
5568
if err := profiles.MergeTo(result); err != nil {
5669
scrapeErrors = append(scrapeErrors, fmt.Errorf("failed to merge profiles from %s: %w", match, err))
5770
continue

receiver/pprofreceiver/internal/file_scraper_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ import (
99

1010
"github.com/stretchr/testify/assert"
1111
"github.com/stretchr/testify/require"
12+
"go.opentelemetry.io/collector/component"
1213
"go.uber.org/zap"
14+
15+
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/pprofreceiver/internal/metadata"
1316
)
1417

1518
func TestScrapeFile(t *testing.T) {
@@ -48,3 +51,28 @@ func TestScrapeMultipleFilesMergesDictionaries(t *testing.T) {
4851
assert.True(t, strings["beta.go"], "merged dictionary missing string from second file")
4952
assert.True(t, strings["inuse_objects"], "merged dictionary missing string from first file")
5053
}
54+
55+
func TestScrapeFile_SetsScopeName(t *testing.T) {
56+
s := FileScraper{
57+
Logger: zap.NewNop(),
58+
Include: filepath.Join(
59+
"testdata",
60+
"pprof.otelcol.alloc_objects.alloc_space.inuse_objects.inuse_space.001.pb.gz",
61+
),
62+
BuildInfo: component.BuildInfo{Version: "1.2.3"},
63+
}
64+
65+
p, err := s.Scrape(t.Context())
66+
require.NoError(t, err)
67+
require.Positive(t, p.ProfileCount())
68+
69+
rp := p.ResourceProfiles().At(0)
70+
sp := rp.ScopeProfiles().At(0)
71+
72+
assert.Equal(
73+
t,
74+
metadata.ScopeName+"/filescraper",
75+
sp.Scope().Name(),
76+
)
77+
require.Equal(t, "1.2.3", sp.Scope().Version())
78+
}

receiver/pprofreceiver/internal/http_scraper.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@ import (
1616
"go.uber.org/zap"
1717

1818
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/pprof"
19+
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/pprofreceiver/internal/metadata"
1920
)
2021

2122
var _ xscraper.Profiles = &HTTPClientScraper{}
2223

2324
type HTTPClientScraper struct {
2425
ClientConfig confighttp.ClientConfig
2526
Settings component.TelemetrySettings
27+
BuildInfo component.BuildInfo
2628
client *http.Client
2729
}
2830

@@ -56,5 +58,19 @@ func (hcs *HTTPClientScraper) ScrapeProfiles(_ context.Context) (pprofile.Profil
5658
if err != nil {
5759
return pprofile.Profiles{}, fmt.Errorf("failed to convert pprof to profiles: %w", err)
5860
}
61+
62+
// set ScopeName and Version
63+
name := metadata.ScopeName + "/httpclientscraper"
64+
version := hcs.BuildInfo.Version
65+
66+
for i := 0; i < profiles.ResourceProfiles().Len(); i++ {
67+
rp := profiles.ResourceProfiles().At(i)
68+
for j := 0; j < rp.ScopeProfiles().Len(); j++ {
69+
sp := rp.ScopeProfiles().At(j)
70+
sp.Scope().SetName(name)
71+
sp.Scope().SetVersion(version)
72+
}
73+
}
74+
5975
return *profiles, err
6076
}

receiver/pprofreceiver/internal/http_scraper_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,11 @@ import (
1414
"time"
1515

1616
"github.com/stretchr/testify/require"
17+
"go.opentelemetry.io/collector/component"
1718
"go.opentelemetry.io/collector/component/componenttest"
1819
"go.opentelemetry.io/collector/config/confighttp"
20+
21+
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/pprofreceiver/internal/metadata"
1922
)
2023

2124
func TestHttpScraper(t *testing.T) {
@@ -37,6 +40,7 @@ func TestHttpScraper(t *testing.T) {
3740

3841
s := HTTPClientScraper{
3942
ClientConfig: confighttp.NewDefaultClientConfig(),
43+
BuildInfo: component.BuildInfo{Version: "1.2.3"},
4044
}
4145
s.ClientConfig.Endpoint = fmt.Sprintf("http://%s/debug/pprof/", listener.Addr().String())
4246
err = s.Start(t.Context(), componenttest.NewNopHost())
@@ -47,4 +51,12 @@ func TestHttpScraper(t *testing.T) {
4751
p, err := s.ScrapeProfiles(t.Context())
4852
require.NoError(t, err)
4953
require.NotEqual(t, 0, p.ProfileCount())
54+
sp := p.ResourceProfiles().At(0).ScopeProfiles().At(0)
55+
56+
require.Equal(
57+
t,
58+
metadata.ScopeName+"/httpclientscraper",
59+
sp.Scope().Name(),
60+
)
61+
require.Equal(t, "1.2.3", sp.Scope().Version())
5062
}

receiver/pprofreceiver/internal/http_server.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"go.uber.org/zap"
2121

2222
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/pprof"
23+
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/pprofreceiver/internal/metadata"
2324
)
2425

2526
// PushPath is the path the pprof push server listens on.
@@ -105,6 +106,18 @@ func (s *HTTPServer) handlePush(w http.ResponseWriter, r *http.Request) {
105106
return
106107
}
107108

109+
// set ScopeName and Version
110+
for i := 0; i < profiles.ResourceProfiles().Len(); i++ {
111+
rp := profiles.ResourceProfiles().At(i)
112+
for j := 0; j < rp.ScopeProfiles().Len(); j++ {
113+
sp := rp.ScopeProfiles().At(j)
114+
115+
// Directly setting values without any extra initialization
116+
sp.Scope().SetName(metadata.ScopeName + "/httpserver")
117+
sp.Scope().SetVersion(s.Settings.BuildInfo.Version)
118+
}
119+
}
120+
108121
ctx := r.Context()
109122
if s.obsrep != nil {
110123
ctx = s.obsrep.StartProfilesOp(ctx)

receiver/pprofreceiver/internal/http_server_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ func TestHTTPServerPush(t *testing.T) {
7676
resp.Body.Close()
7777
assert.Equal(t, http.StatusNoContent, resp.StatusCode)
7878
assert.NotEmpty(t, sink.AllProfiles(), "expected profiles to be consumed")
79+
profiles := sink.AllProfiles()
80+
sp := profiles[0].ResourceProfiles().At(0).ScopeProfiles().At(0)
81+
assert.Equal(t, metadata.ScopeName+"/httpserver", sp.Scope().Name())
82+
assert.Equal(t, "latest", sp.Scope().Version())
7983

8084
// invalid body returns 400
8185
resp = doRequest(http.MethodPost, []byte("not pprof"))

receiver/pprofreceiver/internal/self_scraper.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@ import (
1616
"go.opentelemetry.io/collector/scraper/xscraper"
1717

1818
translator "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/pprof"
19+
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/pprofreceiver/internal/metadata"
1920
)
2021

2122
var _ xscraper.Profiles = &SelfScraper{}
2223

2324
type SelfScraper struct {
2425
BlockProfileFraction int
2526
MutexProfileFraction int
27+
BuildInfo component.BuildInfo
2628
buf *bytes.Buffer
2729
writer *bufio.Writer
2830
}
@@ -48,6 +50,20 @@ func (hcs *SelfScraper) ScrapeProfiles(_ context.Context) (pprofile.Profiles, er
4850
hcs.buf.Reset()
4951
if parseErr == nil {
5052
p, err := translator.ConvertPprofToProfiles(pprofProfile)
53+
54+
if p != nil {
55+
name := metadata.ScopeName + "/selfscraper"
56+
version := hcs.BuildInfo.Version
57+
for i := 0; i < p.ResourceProfiles().Len(); i++ {
58+
rp := p.ResourceProfiles().At(i)
59+
for j := 0; j < rp.ScopeProfiles().Len(); j++ {
60+
sp := rp.ScopeProfiles().At(j)
61+
sp.Scope().SetName(name)
62+
sp.Scope().SetVersion(version)
63+
}
64+
}
65+
}
66+
5167
_ = pprof.StartCPUProfile(hcs.writer)
5268
if p == nil {
5369
return pprofile.Profiles{}, err

receiver/pprofreceiver/internal/self_scraper_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@ import (
77
"testing"
88

99
"github.com/stretchr/testify/require"
10+
"go.opentelemetry.io/collector/component"
1011
"go.opentelemetry.io/collector/component/componenttest"
12+
13+
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/pprofreceiver/internal/metadata"
1114
)
1215

1316
func TestScrapeSelf(t *testing.T) {
1417
s := SelfScraper{
1518
BlockProfileFraction: 1,
1619
MutexProfileFraction: 1,
20+
BuildInfo: component.BuildInfo{Version: "1.2.3"},
1721
}
1822
err := s.Start(t.Context(), componenttest.NewNopHost())
1923
require.NoError(t, err)
@@ -23,4 +27,14 @@ func TestScrapeSelf(t *testing.T) {
2327
p, err := s.ScrapeProfiles(t.Context())
2428
require.NoError(t, err)
2529
require.NotEqual(t, 0, p.ProfileCount())
30+
31+
sp := p.ResourceProfiles().At(0).ScopeProfiles().At(0)
32+
33+
require.Equal(
34+
t,
35+
metadata.ScopeName+"/selfscraper",
36+
sp.Scope().Name(),
37+
)
38+
39+
require.Equal(t, "1.2.3", sp.Scope().Version())
2640
}

0 commit comments

Comments
 (0)