Skip to content

Commit 461babf

Browse files
committed
[pkg/ottl] add profilelocation
This is the next subtype as listed in #40489. Fixes #40163. Signed-off-by: Florian Lehner <[email protected]>
1 parent 147552a commit 461babf

File tree

9 files changed

+755
-0
lines changed

9 files changed

+755
-0
lines changed
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: Add OTTL support for location submessage of OTel Profiling signal.
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: [40163]
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]
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package ctxprofilelocation // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/internal/ctxprofilelocation"
5+
6+
import "go.opentelemetry.io/collector/pdata/pprofile"
7+
8+
const (
9+
Name = "profilelocation"
10+
DocRef = "https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/pkg/ottl/contexts/ottlprofilelocation"
11+
)
12+
13+
type Context interface {
14+
GetProfileLocation() pprofile.Location
15+
GetProfilesDictionary() pprofile.ProfilesDictionary
16+
}
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package ctxprofilelocation // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/internal/ctxprofilelocation"
5+
6+
import (
7+
"context"
8+
"errors"
9+
"math"
10+
11+
"go.opentelemetry.io/collector/pdata/pcommon"
12+
"go.opentelemetry.io/collector/pdata/pprofile"
13+
14+
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
15+
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/internal/ctxerror"
16+
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/internal/ctxutil"
17+
)
18+
19+
var (
20+
errMaxValueExceed = errors.New("exceeded max value")
21+
errInvalidValueType = errors.New("invalid value type")
22+
)
23+
24+
func PathGetSetter[K Context](path ottl.Path[K]) (ottl.GetSetter[K], error) {
25+
if path == nil {
26+
return nil, ctxerror.New("nil", "nil", Name, DocRef)
27+
}
28+
switch path.Name() {
29+
case "mapping_index":
30+
return accessMappingIndex[K](), nil
31+
case "address":
32+
return accessAddress[K](), nil
33+
case "line":
34+
return accessLine[K](), nil
35+
case "attribute_indices":
36+
return accessAttributeIndices[K](), nil
37+
case "attributes":
38+
if path.Keys() == nil {
39+
return accessAttributes[K](), nil
40+
}
41+
return accessAttributesKey(path.Keys()), nil
42+
default:
43+
return nil, ctxerror.New(path.Name(), path.String(), Name, DocRef)
44+
}
45+
}
46+
47+
func accessMappingIndex[K Context]() ottl.StandardGetSetter[K] {
48+
return ottl.StandardGetSetter[K]{
49+
Getter: func(_ context.Context, tCtx K) (any, error) {
50+
return int64(tCtx.GetProfileLocation().MappingIndex()), nil
51+
},
52+
Setter: func(_ context.Context, tCtx K, val any) error {
53+
if v, ok := val.(int64); ok {
54+
if v >= math.MaxInt32 {
55+
return errMaxValueExceed
56+
}
57+
tCtx.GetProfileLocation().SetMappingIndex(int32(v))
58+
return nil
59+
}
60+
return errInvalidValueType
61+
},
62+
}
63+
}
64+
65+
func accessAddress[K Context]() ottl.StandardGetSetter[K] {
66+
return ottl.StandardGetSetter[K]{
67+
Getter: func(_ context.Context, tCtx K) (any, error) {
68+
return uint64(tCtx.GetProfileLocation().Address()), nil
69+
},
70+
Setter: func(_ context.Context, tCtx K, val any) error {
71+
if v, ok := val.(uint64); ok {
72+
tCtx.GetProfileLocation().SetAddress(uint64(v))
73+
return nil
74+
}
75+
return errInvalidValueType
76+
},
77+
}
78+
}
79+
80+
func accessLine[K Context]() ottl.StandardGetSetter[K] {
81+
return ottl.StandardGetSetter[K]{
82+
Getter: func(_ context.Context, tCtx K) (any, error) {
83+
return tCtx.GetProfileLocation().Line(), nil
84+
},
85+
Setter: func(_ context.Context, tCtx K, val any) error {
86+
lines, ok := val.(pprofile.LineSlice)
87+
if !ok {
88+
return errInvalidValueType
89+
}
90+
tCtx.GetProfileLocation().Line().RemoveIf(func(_ pprofile.Line) bool { return true })
91+
_ = lines
92+
for _, line := range lines.All() {
93+
newLine := tCtx.GetProfileLocation().Line().AppendEmpty()
94+
line.CopyTo(newLine)
95+
}
96+
return nil
97+
},
98+
}
99+
}
100+
101+
func accessAttributeIndices[K Context]() ottl.StandardGetSetter[K] {
102+
return ottl.StandardGetSetter[K]{
103+
Getter: func(_ context.Context, tCtx K) (any, error) {
104+
return ctxutil.GetCommonIntSliceValues[int32](tCtx.GetProfileLocation().AttributeIndices()), nil
105+
},
106+
Setter: func(_ context.Context, tCtx K, val any) error {
107+
return ctxutil.SetCommonIntSliceValues[int32](tCtx.GetProfileLocation().AttributeIndices(), val)
108+
},
109+
}
110+
}
111+
112+
func accessAttributes[K Context]() ottl.StandardGetSetter[K] {
113+
return ottl.StandardGetSetter[K]{
114+
Getter: func(_ context.Context, tCtx K) (any, error) {
115+
return pprofile.FromAttributeIndices(tCtx.GetProfilesDictionary().AttributeTable(), tCtx.GetProfileLocation()), nil
116+
},
117+
Setter: func(_ context.Context, tCtx K, val any) error {
118+
m, err := ctxutil.GetMap(val)
119+
if err != nil {
120+
return err
121+
}
122+
tCtx.GetProfileLocation().AttributeIndices().FromRaw([]int32{})
123+
for k, v := range m.All() {
124+
if err := pprofile.PutAttribute(tCtx.GetProfilesDictionary().AttributeTable(), tCtx.GetProfileLocation(), k, v); err != nil {
125+
return err
126+
}
127+
}
128+
return nil
129+
},
130+
}
131+
}
132+
133+
func accessAttributesKey[K Context](key []ottl.Key[K]) ottl.StandardGetSetter[K] {
134+
return ottl.StandardGetSetter[K]{
135+
Getter: func(ctx context.Context, tCtx K) (any, error) {
136+
return ctxutil.GetMapValue[K](ctx, tCtx, pprofile.FromAttributeIndices(tCtx.GetProfilesDictionary().AttributeTable(), tCtx.GetProfileLocation()), key)
137+
},
138+
Setter: func(ctx context.Context, tCtx K, val any) error {
139+
newKey, err := ctxutil.GetMapKeyName(ctx, tCtx, key[0])
140+
if err != nil {
141+
return err
142+
}
143+
v := getAttributeValue(tCtx, *newKey)
144+
if err := ctxutil.SetIndexableValue[K](ctx, tCtx, v, val, key[1:]); err != nil {
145+
return err
146+
}
147+
return pprofile.PutAttribute(tCtx.GetProfilesDictionary().AttributeTable(), tCtx.GetProfileLocation(), *newKey, v)
148+
},
149+
}
150+
}
151+
152+
func getAttributeValue[K Context](tCtx K, key string) pcommon.Value {
153+
// Find the index of the attribute in the profile's attribute indices
154+
// and return the corresponding value from the attribute table.
155+
table := tCtx.GetProfilesDictionary().AttributeTable()
156+
indices := tCtx.GetProfileLocation().AttributeIndices().AsRaw()
157+
158+
for _, tableIndex := range indices {
159+
attr := table.At(int(tableIndex))
160+
if attr.Key() == key {
161+
v := pcommon.NewValueEmpty()
162+
attr.Value().CopyTo(v)
163+
return v
164+
}
165+
}
166+
167+
return pcommon.NewValueEmpty()
168+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package ctxprofilelocation // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/internal/ctxprofilelocation"
5+
6+
import (
7+
"context"
8+
"strings"
9+
"testing"
10+
11+
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/require"
13+
"go.opentelemetry.io/collector/pdata/pprofile"
14+
15+
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
16+
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/internal/pathtest"
17+
)
18+
19+
func TestPathGetSetter(t *testing.T) {
20+
lineSlice := pprofile.NewLineSlice()
21+
for _, lineValue := range []int64{73, 74, 75} {
22+
line := lineSlice.AppendEmpty()
23+
line.SetLine(lineValue)
24+
}
25+
tests := []struct {
26+
path string
27+
val any
28+
keys []ottl.Key[*profileLocationContext]
29+
}{
30+
{
31+
path: "mapping_index",
32+
val: int64(42),
33+
},
34+
{
35+
path: "address",
36+
val: uint64(43),
37+
},
38+
{
39+
path: "line",
40+
val: lineSlice,
41+
},
42+
{
43+
path: "attribute_indices",
44+
val: []int64{97, 98, 99},
45+
},
46+
}
47+
48+
for _, tt := range tests {
49+
t.Run(tt.path, func(t *testing.T) {
50+
pathParts := strings.Split(tt.path, " ")
51+
path := &pathtest.Path[*profileLocationContext]{N: pathParts[0]}
52+
if tt.keys != nil {
53+
path.KeySlice = tt.keys
54+
}
55+
if len(pathParts) > 1 {
56+
path.NextPath = &pathtest.Path[*profileLocationContext]{N: pathParts[1]}
57+
}
58+
59+
location := pprofile.NewLocation()
60+
dictionary := pprofile.NewProfilesDictionary()
61+
62+
accessor, err := PathGetSetter(path)
63+
require.NoError(t, err)
64+
65+
err = accessor.Set(context.Background(), newProfileLocationContext(location, dictionary), tt.val)
66+
require.NoError(t, err)
67+
68+
got, err := accessor.Get(context.Background(), newProfileLocationContext(location, dictionary))
69+
require.NoError(t, err)
70+
71+
assert.Equal(t, tt.val, got)
72+
})
73+
}
74+
}
75+
76+
type profileLocationContext struct {
77+
location pprofile.Location
78+
dictionary pprofile.ProfilesDictionary
79+
}
80+
81+
func (p *profileLocationContext) GetProfilesDictionary() pprofile.ProfilesDictionary {
82+
return p.dictionary
83+
}
84+
85+
func (p *profileLocationContext) GetProfileLocation() pprofile.Location {
86+
return p.location
87+
}
88+
89+
func newProfileLocationContext(location pprofile.Location, dictionary pprofile.ProfilesDictionary) *profileLocationContext {
90+
return &profileLocationContext{location: location, dictionary: dictionary}
91+
}

pkg/ottl/contexts/internal/logprofile/logging.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,33 @@ func (s ProfileSample) MarshalLogObject(encoder zapcore.ObjectEncoder) error {
161161
return joinedErr
162162
}
163163

164+
type ProfileLocation struct {
165+
pprofile.Location
166+
Dictionary pprofile.ProfilesDictionary
167+
}
168+
169+
func (loc ProfileLocation) MarshalLogObject(encoder zapcore.ObjectEncoder) error {
170+
var joinedErr error
171+
172+
encoder.AddInt32("mapping_index", loc.MappingIndex())
173+
encoder.AddUint64("address", loc.Address())
174+
175+
ls := make(lines, 0, loc.Line().Len())
176+
lines := loc.Line().All()
177+
for _, line := range lines {
178+
l, err := newLine(loc.Dictionary, line)
179+
joinedErr = errors.Join(joinedErr, err)
180+
ls = append(ls, l)
181+
}
182+
joinedErr = errors.Join(joinedErr, encoder.AddArray("line", ls))
183+
184+
ats, err := newAttributes(loc.Dictionary, loc.AttributeIndices())
185+
joinedErr = errors.Join(joinedErr, err)
186+
joinedErr = errors.Join(joinedErr, encoder.AddArray("attributes", ats))
187+
188+
return joinedErr
189+
}
190+
164191
type valueTypes []valueType
165192

166193
func (s valueTypes) MarshalLogArray(encoder zapcore.ArrayEncoder) error {
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Profile Location Context
2+
3+
> [!NOTE]
4+
> This documentation applies only to version `0.133.0` and later. Information on earlier versions is not available.
5+
6+
The Profile Location Context is a Context implementation for [pdata Profiles](https://github.com/open-telemetry/opentelemetry-collector/tree/main/pdata/pprofile), the collector's internal representation for OTLP profile data. This Context should be used when interacted with OTLP profiles.
7+
8+
## Paths
9+
In general, the Profile Location Context supports accessing pdata using the field names from the [profiles proto](https://github.com/open-telemetry/opentelemetry-proto/blob/main/opentelemetry/proto/profiles/v1development/profiles.proto). All integers are returned and set via `int64`. All doubles are returned and set via `float64`.
10+
11+
The following paths are supported.
12+
13+
| path | field accessed | type |
14+
|------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------|
15+
| cache | the value of the current transform context's temporary cache. cache can be used as a temporary placeholder for data during complex transformations | pcommon.Map |
16+
| cache\[""\] | the value of an item in cache. Supports multiple indexes to access nested fields | string, bool, int64, float64, pcommon.Map, pcommon.Slice, []byte or nil |
17+
| resource | resource of the profile being processed | pcommon.Resource |
18+
| resource.attributes | resource attributes of the profile being processed | pcommon.Map |
19+
| resource.attributes\[""\] | the value of the resource attribute of the profile being processed. Supports multiple indexes to access nested fields | string, bool, int64, float64, pcommon.Map, pcommon.Slice, []byte or nil |
20+
| instrumentation_scope | instrumentation scope of the profile being processed | pcommon.InstrumentationScope |
21+
| instrumentation_scope.name | name of the instrumentation scope of the profile being processed | string |
22+
| instrumentation_scope.version | version of the instrumentation scope of the profile being processed | string |
23+
| instrumentation_scope.attributes | instrumentation scope attributes of the data point being processed | pcommon.Map |
24+
| instrumentation_scope.attributes\[""\] | the value of the instrumentation scope attribute of the data point being processed. Supports multiple indexes to access nested fields | string, bool, int64, float64, pcommon.Map, pcommon.Slice, []byte or nil |
25+
| profilelocation.attributes | attributes of the profile being processed | pcommon.Map |
26+
| profilelocation.attributes\[""\] | the value of the attribute of the profile being processed. Supports multiple indexes to access nested fields | string, bool, int64, float64, pcommon.Map, pcommon.Slice, []byte or nil |
27+
| profilelocation.attribute_indices | the attribute indices of the location being processed | []int64 |
28+
| profilelocation.mapping_index | reference to mapping in ProfilesDictionary.mapping_table | int64 |
29+
| profilelocation.address | the instruction address for this location, if available | int64 |
30+
| profilelocation.line | reference to one or more lines in source code | profile.LineSlice |
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package ottlprofilelocation
5+
6+
import (
7+
"testing"
8+
9+
"go.uber.org/goleak"
10+
)
11+
12+
func TestMain(m *testing.M) {
13+
goleak.VerifyTestMain(m)
14+
}

0 commit comments

Comments
 (0)