Skip to content

Commit c23786d

Browse files
committed
[pkg/ottl]: Create ctxprofilecommon for common attribute handling in various profiling sub messages
Most Profiling messages do have some attributes. Create ctxprofilecommon for shared functionality. Signed-off-by: Florian Lehner <florian.lehner@elastic.co>
1 parent f9e1653 commit c23786d

File tree

11 files changed

+308
-127
lines changed

11 files changed

+308
-127
lines changed

.chloggen/ctxprofilecommon.yaml

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. filelogreceiver)
7+
component: pkg/ottl
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Create ctxprofilecommon for common attribute handling in various profiling sub messages
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: []
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+
# 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: [api]

pkg/ottl/contexts/internal/ctxprofile/context.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
package ctxprofile // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/internal/ctxprofile"
55
import (
6+
"go.opentelemetry.io/collector/pdata/pcommon"
67
"go.opentelemetry.io/collector/pdata/pprofile"
78
)
89

@@ -12,6 +13,7 @@ const (
1213
)
1314

1415
type Context interface {
16+
AttributeIndices() pcommon.Int32Slice
1517
GetProfile() pprofile.Profile
1618
GetProfilesDictionary() pprofile.ProfilesDictionary
1719
}

pkg/ottl/contexts/internal/ctxprofile/profile.go

Lines changed: 3 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
1616
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/internal/ctxcommon"
1717
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/internal/ctxerror"
18+
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/internal/ctxprofilecommon"
1819
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/internal/ctxutil"
1920
)
2021

@@ -64,9 +65,9 @@ func PathGetSetter[K Context](path ottl.Path[K]) (ottl.GetSetter[K], error) {
6465
return accessOriginalPayload[K](), nil
6566
case "attributes":
6667
if path.Keys() == nil {
67-
return accessAttributes[K](), nil
68+
return ctxprofilecommon.AccessAttributes[K](), nil
6869
}
69-
return accessAttributesKey(path.Keys()), nil
70+
return ctxprofilecommon.AccessAttributesKey[K](path.Keys()), nil
7071
default:
7172
return nil, ctxerror.New(path.Name(), path.String(), Name, DocRef)
7273
}
@@ -311,62 +312,3 @@ func accessOriginalPayload[K Context]() ottl.StandardGetSetter[K] {
311312
},
312313
}
313314
}
314-
315-
func accessAttributes[K Context]() ottl.StandardGetSetter[K] {
316-
return ottl.StandardGetSetter[K]{
317-
Getter: func(_ context.Context, tCtx K) (any, error) {
318-
return pprofile.FromAttributeIndices(tCtx.GetProfilesDictionary().AttributeTable(), tCtx.GetProfile()), nil
319-
},
320-
Setter: func(_ context.Context, tCtx K, val any) error {
321-
m, err := ctxutil.GetMap(val)
322-
if err != nil {
323-
return err
324-
}
325-
tCtx.GetProfile().AttributeIndices().FromRaw([]int32{})
326-
for k, v := range m.All() {
327-
if err := pprofile.PutAttribute(tCtx.GetProfilesDictionary().AttributeTable(), tCtx.GetProfile(), k, v); err != nil {
328-
return err
329-
}
330-
}
331-
return nil
332-
},
333-
}
334-
}
335-
336-
func accessAttributesKey[K Context](key []ottl.Key[K]) ottl.StandardGetSetter[K] {
337-
return ottl.StandardGetSetter[K]{
338-
Getter: func(ctx context.Context, tCtx K) (any, error) {
339-
return ctxutil.GetMapValue[K](ctx, tCtx, pprofile.FromAttributeIndices(tCtx.GetProfilesDictionary().AttributeTable(), tCtx.GetProfile()), key)
340-
},
341-
Setter: func(ctx context.Context, tCtx K, val any) error {
342-
newKey, err := ctxutil.GetMapKeyName(ctx, tCtx, key[0])
343-
if err != nil {
344-
return err
345-
}
346-
v := getAttributeValue(tCtx, *newKey)
347-
err = ctxutil.SetIndexableValue[K](ctx, tCtx, v, val, key[1:])
348-
if err != nil {
349-
return err
350-
}
351-
return pprofile.PutAttribute(tCtx.GetProfilesDictionary().AttributeTable(), tCtx.GetProfile(), *newKey, v)
352-
},
353-
}
354-
}
355-
356-
func getAttributeValue[K Context](tCtx K, key string) pcommon.Value {
357-
// Find the index of the attribute in the profile's attribute indices
358-
// and return the corresponding value from the attribute table.
359-
table := tCtx.GetProfilesDictionary().AttributeTable()
360-
indices := tCtx.GetProfile().AttributeIndices().AsRaw()
361-
362-
for _, tableIndex := range indices {
363-
attr := table.At(int(tableIndex))
364-
if attr.Key() == key {
365-
v := pcommon.NewValueEmpty()
366-
attr.Value().CopyTo(v)
367-
return v
368-
}
369-
}
370-
371-
return pcommon.NewValueEmpty()
372-
}

pkg/ottl/contexts/internal/ctxprofile/profile_test.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,8 +205,15 @@ func (p *profileContext) GetProfile() pprofile.Profile {
205205
return p.profile
206206
}
207207

208+
func (p *profileContext) AttributeIndices() pcommon.Int32Slice {
209+
return p.profile.AttributeIndices()
210+
}
211+
208212
func newProfileContext(profile pprofile.Profile, dictionary pprofile.ProfilesDictionary) *profileContext {
209-
return &profileContext{profile: profile, dictionary: dictionary}
213+
return &profileContext{
214+
profile: profile,
215+
dictionary: dictionary,
216+
}
210217
}
211218

212219
func createValueTypeSlice() pprofile.ValueTypeSlice {
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package ctxprofilecommon // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/internal/ctxprofilecommon"
5+
6+
import (
7+
"context"
8+
9+
"go.opentelemetry.io/collector/pdata/pcommon"
10+
"go.opentelemetry.io/collector/pdata/pprofile"
11+
12+
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
13+
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/internal/ctxutil"
14+
)
15+
16+
type ProfileAttributeContext interface {
17+
AttributeIndices() pcommon.Int32Slice
18+
GetProfilesDictionary() pprofile.ProfilesDictionary
19+
}
20+
21+
func AccessAttributes[K ProfileAttributeContext]() ottl.StandardGetSetter[K] {
22+
return ottl.StandardGetSetter[K]{
23+
Getter: func(_ context.Context, tCtx K) (any, error) {
24+
return fromAttributeIndices(tCtx.GetProfilesDictionary().AttributeTable(), tCtx), nil
25+
},
26+
Setter: func(_ context.Context, tCtx K, val any) error {
27+
m, err := ctxutil.GetMap(val)
28+
if err != nil {
29+
return err
30+
}
31+
for k, v := range m.All() {
32+
if err := putAttribute(tCtx.GetProfilesDictionary().AttributeTable(), tCtx, k, v); err != nil {
33+
return err
34+
}
35+
}
36+
return nil
37+
},
38+
}
39+
}
40+
41+
func AccessAttributesKey[K ProfileAttributeContext](key []ottl.Key[K]) ottl.StandardGetSetter[K] {
42+
return ottl.StandardGetSetter[K]{
43+
Getter: func(ctx context.Context, tCtx K) (any, error) {
44+
return ctxutil.GetMapValue[K](ctx, tCtx, fromAttributeIndices(tCtx.GetProfilesDictionary().AttributeTable(), tCtx), key)
45+
},
46+
Setter: func(ctx context.Context, tCtx K, val any) error {
47+
newKey, err := ctxutil.GetMapKeyName(ctx, tCtx, key[0])
48+
if err != nil {
49+
return err
50+
}
51+
v := getAttributeValue(tCtx, *newKey)
52+
if err := ctxutil.SetIndexableValue[K](ctx, tCtx, v, val, key[1:]); err != nil {
53+
return err
54+
}
55+
return putAttribute(tCtx.GetProfilesDictionary().AttributeTable(), tCtx, *newKey, v)
56+
},
57+
}
58+
}
59+
60+
func getAttributeValue[K ProfileAttributeContext](tCtx K, key string) pcommon.Value {
61+
// Find the index of the attribute in the profile's attribute indices
62+
// and return the corresponding value from the attribute table.
63+
table := tCtx.GetProfilesDictionary().AttributeTable()
64+
indices := tCtx.AttributeIndices().AsRaw()
65+
66+
for _, tableIndex := range indices {
67+
attr := table.At(int(tableIndex))
68+
if attr.Key() == key {
69+
v := pcommon.NewValueEmpty()
70+
attr.Value().CopyTo(v)
71+
return v
72+
}
73+
}
74+
75+
return pcommon.NewValueEmpty()
76+
}
77+
78+
// fromAttributeIndices creates a pcommon.Map from the attribute indices in the provided context
79+
func fromAttributeIndices(attributeTable pprofile.AttributeTableSlice, ctx ProfileAttributeContext) pcommon.Map {
80+
m := pcommon.NewMap()
81+
indices := ctx.AttributeIndices().AsRaw()
82+
83+
for _, tableIndex := range indices {
84+
if int(tableIndex) < attributeTable.Len() {
85+
attr := attributeTable.At(int(tableIndex))
86+
attr.Value().CopyTo(m.PutEmpty(attr.Key()))
87+
}
88+
}
89+
90+
return m
91+
}
92+
93+
// putAttribute adds or updates an attribute in the attribute table and updates the context's attribute indices
94+
func putAttribute(attributeTable pprofile.AttributeTableSlice, ctx ProfileAttributeContext, key string, value pcommon.Value) error {
95+
// First, check if the attribute already exists in the context's indices
96+
indices := ctx.AttributeIndices()
97+
existingIndex := -1
98+
99+
for i := 0; i < indices.Len(); i++ {
100+
tableIndex := indices.At(i)
101+
if int(tableIndex) < attributeTable.Len() {
102+
attr := attributeTable.At(int(tableIndex))
103+
if attr.Key() == key {
104+
existingIndex = int(tableIndex)
105+
break
106+
}
107+
}
108+
}
109+
110+
if existingIndex >= 0 {
111+
// Update existing attribute
112+
attr := attributeTable.At(existingIndex)
113+
value.CopyTo(attr.Value())
114+
} else {
115+
// Add new attribute to the table
116+
newAttr := attributeTable.AppendEmpty()
117+
newAttr.SetKey(key)
118+
value.CopyTo(newAttr.Value())
119+
120+
// Add the new index to the context's attribute indices
121+
newIndex := int32(attributeTable.Len() - 1)
122+
indices.Append(newIndex)
123+
}
124+
125+
return nil
126+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package ctxprofilecommon // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/internal/ctxprofilecommon"
5+
6+
import (
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
"go.opentelemetry.io/collector/pdata/pcommon"
11+
"go.opentelemetry.io/collector/pdata/pprofile"
12+
)
13+
14+
// Mock implementations for AttributeContext and dependencies
15+
16+
type mockAttributeContext struct {
17+
indices pcommon.Int32Slice
18+
dictionary pprofile.ProfilesDictionary
19+
}
20+
21+
func (m *mockAttributeContext) AttributeIndices() pcommon.Int32Slice {
22+
return m.indices
23+
}
24+
25+
func (m *mockAttributeContext) GetProfilesDictionary() pprofile.ProfilesDictionary {
26+
return m.dictionary
27+
}
28+
29+
func TestAccessAttributes_Getter(t *testing.T) {
30+
dict := pprofile.NewProfilesDictionary()
31+
attrTable := dict.AttributeTable()
32+
attr1 := attrTable.AppendEmpty()
33+
attr1.SetKey("foo")
34+
attr1.Value().SetStr("bar")
35+
attr2 := attrTable.AppendEmpty()
36+
attr2.SetKey("baz")
37+
attr2.Value().SetInt(42)
38+
39+
indices := pcommon.NewInt32Slice()
40+
indices.Append(0)
41+
indices.Append(1)
42+
43+
ctx := &mockAttributeContext{
44+
indices: indices,
45+
dictionary: dict,
46+
}
47+
48+
getSetter := AccessAttributes[*mockAttributeContext]()
49+
50+
got, err := getSetter.Getter(t.Context(), ctx)
51+
assert.NoError(t, err)
52+
53+
m, ok := got.(pcommon.Map)
54+
assert.True(t, ok)
55+
56+
fooValue, ok := m.Get("foo")
57+
assert.True(t, ok)
58+
assert.Equal(t, "bar", fooValue.Str())
59+
60+
bazValue, ok := m.Get("baz")
61+
assert.True(t, ok)
62+
assert.Equal(t, int64(42), bazValue.Int())
63+
}
64+
65+
func TestAccessAttributes_Setter(t *testing.T) {
66+
dict := pprofile.NewProfilesDictionary()
67+
attrTable := dict.AttributeTable()
68+
indices := pcommon.NewInt32Slice()
69+
70+
ctx := &mockAttributeContext{
71+
indices: indices,
72+
dictionary: dict,
73+
}
74+
75+
getSetter := AccessAttributes[*mockAttributeContext]()
76+
77+
// Prepare map to set
78+
m := pcommon.NewMap()
79+
m.PutStr("alpha", "beta")
80+
m.PutInt("num", 123)
81+
82+
err := getSetter.Setter(t.Context(), ctx, m)
83+
assert.NoError(t, err)
84+
85+
// Check that attributes were set in the table
86+
foundAlpha := false
87+
foundNum := false
88+
for i := 0; i < attrTable.Len(); i++ {
89+
attr := attrTable.At(i)
90+
if attr.Key() == "alpha" {
91+
foundAlpha = true
92+
assert.Equal(t, "beta", attr.Value().Str())
93+
}
94+
if attr.Key() == "num" {
95+
foundNum = true
96+
assert.Equal(t, int64(123), attr.Value().Int())
97+
}
98+
}
99+
assert.True(t, foundAlpha)
100+
assert.True(t, foundNum)
101+
}
102+
103+
func TestAccessAttributes_Setter_InvalidValue(t *testing.T) {
104+
dict := pprofile.NewProfilesDictionary()
105+
indices := pcommon.NewInt32Slice()
106+
107+
ctx := &mockAttributeContext{
108+
indices: indices,
109+
dictionary: dict,
110+
}
111+
112+
getSetter := AccessAttributes[*mockAttributeContext]()
113+
114+
// Pass a value that is not a ctxutil.Map
115+
err := getSetter.Setter(t.Context(), ctx, "not_a_map")
116+
assert.Error(t, err)
117+
}

0 commit comments

Comments
 (0)