Skip to content

Commit 6745566

Browse files
committed
[pkg/ottl] Accessors for profile attributes
1 parent 521d9e5 commit 6745566

File tree

4 files changed

+181
-1
lines changed

4 files changed

+181
-1
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 accessors for profile attributes
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: [39681]
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: [user]

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

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"context"
88
"encoding/hex"
99
"errors"
10+
"fmt"
11+
"math"
1012
"time"
1113

1214
"go.opentelemetry.io/collector/pdata/pcommon"
@@ -80,6 +82,11 @@ func PathGetSetter[K ProfileContext](path ottl.Path[K]) (ottl.GetSetter[K], erro
8082
return accessOriginalPayloadFormat[K](), nil
8183
case "original_payload":
8284
return accessOriginalPayload[K](), nil
85+
case "attributes":
86+
if path.Keys() == nil {
87+
return accessAttributes[K](), nil
88+
}
89+
return accessAttributesKey(path.Keys()), nil
8390
default:
8491
return nil, ctxerror.New(path.Name(), path.String(), Name, DocRef)
8592
}
@@ -419,3 +426,124 @@ func accessOriginalPayload[K ProfileContext]() ottl.StandardGetSetter[K] {
419426
},
420427
}
421428
}
429+
430+
func accessAttributes[K ProfileContext]() ottl.StandardGetSetter[K] {
431+
return ottl.StandardGetSetter[K]{
432+
Getter: func(_ context.Context, tCtx K) (any, error) {
433+
return pprofile.FromAttributeIndices(tCtx.GetProfile().AttributeTable(), tCtx.GetProfile()), nil
434+
},
435+
Setter: func(_ context.Context, tCtx K, val any) error {
436+
m, ok := val.(pcommon.Map)
437+
if !ok {
438+
return fmt.Errorf("expected pcommon.Map, got %T", val)
439+
}
440+
tCtx.GetProfile().AttributeIndices().FromRaw([]int32{})
441+
for k, v := range m.All() {
442+
if err := PutAttribute(tCtx.GetProfile().AttributeTable(), tCtx.GetProfile(), k, v); err != nil {
443+
return err
444+
}
445+
}
446+
return nil
447+
},
448+
}
449+
}
450+
451+
func accessAttributesKey[K Context](key []ottl.Key[K]) ottl.StandardGetSetter[K] {
452+
return ottl.StandardGetSetter[K]{
453+
Getter: func(ctx context.Context, tCtx K) (any, error) {
454+
return ctxutil.GetMapValue[K](ctx, tCtx, pprofile.FromAttributeIndices(tCtx.GetProfile().AttributeTable(), tCtx.GetProfile()), key)
455+
},
456+
Setter: func(ctx context.Context, tCtx K, val any) error {
457+
newKey, err := key[0].String(ctx, tCtx)
458+
if err != nil {
459+
return err
460+
}
461+
v := pcommon.NewValueEmpty()
462+
if err = v.FromRaw(val); err != nil {
463+
return err
464+
}
465+
return PutAttribute(tCtx.GetProfile().AttributeTable(), tCtx.GetProfile(), *newKey, v)
466+
},
467+
}
468+
}
469+
470+
// TODO: Remove the following code once https://github.com/open-telemetry/opentelemetry-collector/pull/12798 is merged.
471+
472+
type attributable interface {
473+
AttributeIndices() pcommon.Int32Slice
474+
}
475+
476+
var errTooManyTableEntries = errors.New("too many entries in AttributeTable")
477+
478+
// PutAttribute updates an AttributeTable and a record's AttributeIndices to
479+
// add a new attribute.
480+
// The record can be any struct that implements an `AttributeIndices` method.
481+
func PutAttribute(table pprofile.AttributeTableSlice, record attributable, key string, value pcommon.Value) error {
482+
for i := range record.AttributeIndices().Len() {
483+
idx := int(record.AttributeIndices().At(i))
484+
if idx < 0 || idx >= table.Len() {
485+
return fmt.Errorf("index value %d out of range in AttributeIndices[%d]", idx, i)
486+
}
487+
attr := table.At(idx)
488+
if attr.Key() == key {
489+
if attr.Value().Equal(value) {
490+
// Attribute already exists, nothing to do.
491+
return nil
492+
}
493+
494+
// If the attribute table already contains the key/value pair, just update the index.
495+
for j := range table.Len() {
496+
a := table.At(j)
497+
if a.Key() == key && a.Value().Equal(value) {
498+
if j > math.MaxInt32 {
499+
return errTooManyTableEntries
500+
}
501+
record.AttributeIndices().SetAt(i, int32(j))
502+
return nil
503+
}
504+
}
505+
506+
if table.Len() >= math.MaxInt32 {
507+
return errTooManyTableEntries
508+
}
509+
510+
// Add the key/value pair as a new attribute to the table...
511+
entry := table.AppendEmpty()
512+
entry.SetKey(key)
513+
value.CopyTo(entry.Value())
514+
515+
// ...and update the existing index.
516+
record.AttributeIndices().SetAt(i, int32(table.Len()-1))
517+
return nil
518+
}
519+
}
520+
521+
if record.AttributeIndices().Len() >= math.MaxInt32 {
522+
return errors.New("too many entries in AttributeIndices")
523+
}
524+
525+
for j := range table.Len() {
526+
a := table.At(j)
527+
if a.Key() == key && a.Value().Equal(value) {
528+
if j > math.MaxInt32 {
529+
return errTooManyTableEntries
530+
}
531+
// Add the index of the existing attribute to the indices.
532+
record.AttributeIndices().Append(int32(j))
533+
return nil
534+
}
535+
}
536+
537+
if table.Len() >= math.MaxInt32 {
538+
return errTooManyTableEntries
539+
}
540+
541+
// Add the key/value pair as a new attribute to the table...
542+
entry := table.AppendEmpty()
543+
entry.SetKey(key)
544+
value.CopyTo(entry.Value())
545+
546+
// ...and add a new index to the indices.
547+
record.AttributeIndices().Append(int32(table.Len() - 1))
548+
return nil
549+
}

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

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,17 @@ import (
1414
"go.opentelemetry.io/collector/pdata/pcommon"
1515
"go.opentelemetry.io/collector/pdata/pprofile"
1616

17+
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
1718
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/internal/pathtest"
19+
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/ottltest"
1820
)
1921

2022
func TestPathGetSetter(t *testing.T) {
2123
// create tests
2224
tests := []struct {
2325
path string
2426
val any
27+
keys []ottl.Key[*profileContext]
2528
setFails bool
2629
}{
2730
{
@@ -138,19 +141,39 @@ func TestPathGetSetter(t *testing.T) {
138141
path: "original_payload",
139142
val: []byte{1, 2, 3},
140143
},
144+
{
145+
path: "attributes",
146+
val: func() pcommon.Map {
147+
m := pcommon.NewMap()
148+
m.PutStr("akey", "val")
149+
return m
150+
}(),
151+
},
152+
{
153+
path: "attributes",
154+
keys: []ottl.Key[*profileContext]{
155+
&pathtest.Key[*profileContext]{
156+
S: ottltest.Strp("akey"),
157+
},
158+
},
159+
val: "val",
160+
},
141161
}
142162

143163
for _, tt := range tests {
144164
t.Run(tt.path, func(t *testing.T) {
145165
pathParts := strings.Split(tt.path, " ")
146166
path := &pathtest.Path[*profileContext]{N: pathParts[0]}
167+
if tt.keys != nil {
168+
path.KeySlice = tt.keys
169+
}
147170
if len(pathParts) > 1 {
148171
path.NextPath = &pathtest.Path[*profileContext]{N: pathParts[1]}
149172
}
150173

151174
profile := pprofile.NewProfile()
152175

153-
accessor, err := PathGetSetter[*profileContext](path)
176+
accessor, err := PathGetSetter(path)
154177
require.NoError(t, err)
155178

156179
err = accessor.Set(context.Background(), newProfileContext(profile), tt.val)

pkg/ottl/contexts/ottlprofile/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ The following paths are supported.
2222
| instrumentation_scope.version | version of the instrumentation scope of the profile being processed | string |
2323
| instrumentation_scope.attributes | instrumentation scope attributes of the data point being processed | pcommon.Map |
2424
| 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+
| profile.attributes | attributes of the profile being processed | pcommon.Map |
26+
| profile.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 |
2527
| profile.sample_type | the sample types of the profile being processed | pprofile.ValueTypeSlice |
2628
| profile.sample | the samples of the profile being processed | pprofile.SampleSlice |
2729
| profile.mapping_table | the mapping table of the profile being processed | pprofile.MappingSlice |

0 commit comments

Comments
 (0)