Skip to content

Commit 749f997

Browse files
rockdabootdmathieu
andauthored
Introduce pprofile.PutAttribute helper (#12798)
#### Description This introduces the `pprofile.PutAttribute()` helper method so profile extensions can modify attributes. It replaces the yet unused `pprofile.AddAttribute()` function, as `Add` doesn't resonate well with the `pcommon.Map` `Put*` functions. Additionally, the `PutAttributes()` helper takes into account the map k/v nature of attributes, while `AddAttributes()` didn't do this. #### Testing ``` goos: linux goarch: amd64 pkg: go.opentelemetry.io/collector/pdata/pprofile cpu: 12th Gen Intel(R) Core(TM) i7-12800H │ old.txt │ new.txt │ │ sec/op │ sec/op vs base │ AddAttribute/with_a_new_string_attribute-20 27.66n ± 3% AddAttribute/with_an_existing_attribute-20 27.18n ± 4% AddAttribute/with_a_duplicate_attribute-20 27.18n ± 2% AddAttribute/with_a_hundred_attributes_to_loop_through-20 103.0n ± 4% PutAttribute/with_a_new_string_attribute-20 27.71n ± 2% PutAttribute/with_an_existing_attribute-20 28.14n ± 1% PutAttribute/with_a_duplicate_attribute-20 28.14n ± 2% PutAttribute/with_a_hundred_attributes_to_loop_through-20 28.15n ± 2% geomean 38.08n 28.04n ? ¹ ² ¹ benchmark set differs from baseline; geomeans may not be comparable ² ratios must be >0 to compute geomean │ old.txt │ new.txt │ │ B/op │ B/op vs base │ AddAttribute/with_a_new_string_attribute-20 16.00 ± 0% AddAttribute/with_an_existing_attribute-20 16.00 ± 0% AddAttribute/with_a_duplicate_attribute-20 16.00 ± 0% AddAttribute/with_a_hundred_attributes_to_loop_through-20 16.00 ± 0% PutAttribute/with_a_new_string_attribute-20 16.00 ± 0% PutAttribute/with_an_existing_attribute-20 16.00 ± 0% PutAttribute/with_a_duplicate_attribute-20 16.00 ± 0% PutAttribute/with_a_hundred_attributes_to_loop_through-20 16.00 ± 0% geomean 16.00 16.00 ? ¹ ² ¹ benchmark set differs from baseline; geomeans may not be comparable ² ratios must be >0 to compute geomean │ old.txt │ new.txt │ │ allocs/op │ allocs/op vs base │ AddAttribute/with_a_new_string_attribute-20 1.000 ± 0% AddAttribute/with_an_existing_attribute-20 1.000 ± 0% AddAttribute/with_a_duplicate_attribute-20 1.000 ± 0% AddAttribute/with_a_hundred_attributes_to_loop_through-20 1.000 ± 0% PutAttribute/with_a_new_string_attribute-20 1.000 ± 0% PutAttribute/with_an_existing_attribute-20 1.000 ± 0% PutAttribute/with_a_duplicate_attribute-20 1.000 ± 0% PutAttribute/with_a_hundred_attributes_to_loop_through-20 1.000 ± 0% geomean 1.000 1.000 ? ¹ ² ¹ benchmark set differs from baseline; geomeans may not be comparable ² ratios must be >0 to compute geomean ``` See also #12390 --------- Co-authored-by: Damien Mathieu <[email protected]>
1 parent d1a4411 commit 749f997

File tree

3 files changed

+209
-28
lines changed

3 files changed

+209
-28
lines changed

.chloggen/profiles-PutAttributes.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: deprecation
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. otlpreceiver)
7+
component: pdata/profile
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Replace AddAttribute with the PutAttribute helper method to modify the content of attributable records.
11+
12+
# One or more tracking issues or pull requests related to the change
13+
issues: [12798]
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: [api]

pdata/pprofile/attributes.go

Lines changed: 79 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ type attributable interface {
1717

1818
// FromAttributeIndices builds a [pcommon.Map] containing the attributes of a
1919
// record.
20-
// The record can by any struct that implements an `AttributeIndices` method.
20+
// The record can be any struct that implements an `AttributeIndices` method.
2121
// Updates made to the return map will not be applied back to the record.
2222
func FromAttributeIndices(table AttributeTableSlice, record attributable) pcommon.Map {
2323
m := pcommon.NewMap()
@@ -31,9 +31,7 @@ func FromAttributeIndices(table AttributeTableSlice, record attributable) pcommo
3131
return m
3232
}
3333

34-
// AddAttribute updates an AttributeTable and a record's AttributeIndices to
35-
// add a new attribute.
36-
// The record can by any struct that implements an `AttributeIndices` method.
34+
// Deprecated: [v0.126.0] Use PutAttribute instead.
3735
func AddAttribute(table AttributeTableSlice, record attributable, key string, value pcommon.Value) error {
3836
for i := range table.Len() {
3937
a := table.At(i)
@@ -66,3 +64,80 @@ func AddAttribute(table AttributeTableSlice, record attributable, key string, va
6664

6765
return nil
6866
}
67+
68+
var errTooManyTableEntries = errors.New("too many entries in AttributeTable")
69+
70+
// PutAttribute updates an AttributeTable and a record's AttributeIndices to
71+
// add or update an attribute.
72+
// The assumption is that attributes are a map as for other signals (metrics, logs, etc.), thus
73+
// the same key must not appear twice in a list of attributes / attribute indices.
74+
// The record can be any struct that implements an `AttributeIndices` method.
75+
func PutAttribute(table AttributeTableSlice, record attributable, key string, value pcommon.Value) error {
76+
for i := range record.AttributeIndices().Len() {
77+
idx := int(record.AttributeIndices().At(i))
78+
if idx < 0 || idx >= table.Len() {
79+
return fmt.Errorf("index value %d out of range in AttributeIndices[%d]", idx, i)
80+
}
81+
attr := table.At(idx)
82+
if attr.Key() == key {
83+
if attr.Value().Equal(value) {
84+
// Attribute already exists, nothing to do.
85+
return nil
86+
}
87+
88+
// If the attribute table already contains the key/value pair, just update the index.
89+
for j := range table.Len() {
90+
a := table.At(j)
91+
if a.Key() == key && a.Value().Equal(value) {
92+
if j > math.MaxInt32 {
93+
return errTooManyTableEntries
94+
}
95+
record.AttributeIndices().SetAt(i, int32(j)) //nolint:gosec // overflow checked
96+
return nil
97+
}
98+
}
99+
100+
if table.Len() >= math.MaxInt32 {
101+
return errTooManyTableEntries
102+
}
103+
104+
// Add the key/value pair as a new attribute to the table...
105+
entry := table.AppendEmpty()
106+
entry.SetKey(key)
107+
value.CopyTo(entry.Value())
108+
109+
// ...and update the existing index.
110+
record.AttributeIndices().SetAt(i, int32(table.Len()-1)) //nolint:gosec // overflow checked
111+
return nil
112+
}
113+
}
114+
115+
if record.AttributeIndices().Len() >= math.MaxInt32 {
116+
return errors.New("too many entries in AttributeIndices")
117+
}
118+
119+
for j := range table.Len() {
120+
a := table.At(j)
121+
if a.Key() == key && a.Value().Equal(value) {
122+
if j > math.MaxInt32 {
123+
return errTooManyTableEntries
124+
}
125+
// Add the index of the existing attribute to the indices.
126+
record.AttributeIndices().Append(int32(j)) //nolint:gosec // overflow checked
127+
return nil
128+
}
129+
}
130+
131+
if table.Len() >= math.MaxInt32 {
132+
return errTooManyTableEntries
133+
}
134+
135+
// Add the key/value pair as a new attribute to the table...
136+
entry := table.AppendEmpty()
137+
entry.SetKey(key)
138+
value.CopyTo(entry.Value())
139+
140+
// ...and add a new index to the indices.
141+
record.AttributeIndices().Append(int32(table.Len() - 1)) //nolint:gosec // overflow checked
142+
return nil
143+
}

pdata/pprofile/attributes_test.go

Lines changed: 105 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,36 @@ import (
1313
"go.opentelemetry.io/collector/pdata/pcommon"
1414
)
1515

16+
func TestAddAttribute(t *testing.T) {
17+
table := NewAttributeTableSlice()
18+
att := table.AppendEmpty()
19+
att.SetKey("hello")
20+
att.Value().SetStr("world")
21+
22+
// Add a brand new attribute
23+
loc := NewLocation()
24+
err := AddAttribute(table, loc, "bonjour", pcommon.NewValueStr("monde"))
25+
require.NoError(t, err)
26+
27+
assert.Equal(t, 2, table.Len())
28+
assert.Equal(t, []int32{1}, loc.AttributeIndices().AsRaw())
29+
30+
// Add an already existing attribute
31+
mapp := NewMapping()
32+
err = AddAttribute(table, mapp, "hello", pcommon.NewValueStr("world"))
33+
require.NoError(t, err)
34+
35+
assert.Equal(t, 2, table.Len())
36+
assert.Equal(t, []int32{0}, mapp.AttributeIndices().AsRaw())
37+
38+
// Add a duplicate attribute
39+
err = AddAttribute(table, mapp, "hello", pcommon.NewValueStr("world"))
40+
require.NoError(t, err)
41+
42+
assert.Equal(t, 2, table.Len())
43+
assert.Equal(t, []int32{0}, mapp.AttributeIndices().AsRaw())
44+
}
45+
1646
func TestFromAttributeIndices(t *testing.T) {
1747
table := NewAttributeTableSlice()
1848
att := table.AppendEmpty()
@@ -44,34 +74,85 @@ func TestFromAttributeIndices(t *testing.T) {
4474
assert.Equal(t, attrs.AsRaw(), m)
4575
}
4676

47-
func TestAddAttribute(t *testing.T) {
77+
func testPutAttribute(t *testing.T, record attributable) {
4878
table := NewAttributeTableSlice()
49-
att := table.AppendEmpty()
50-
att.SetKey("hello")
51-
att.Value().SetStr("world")
52-
53-
// Add a brand new attribute
54-
loc := NewLocation()
55-
err := AddAttribute(table, loc, "bonjour", pcommon.NewValueStr("monde"))
56-
require.NoError(t, err)
5779

80+
// Put a first attribute.
81+
require.NoError(t, PutAttribute(table, record, "hello", pcommon.NewValueStr("world")))
82+
assert.Equal(t, 1, table.Len())
83+
assert.Equal(t, []int32{0}, record.AttributeIndices().AsRaw())
84+
85+
// Put an attribute, same key, same value.
86+
// This should be a no-op.
87+
require.NoError(t, PutAttribute(table, record, "hello", pcommon.NewValueStr("world")))
88+
assert.Equal(t, 1, table.Len())
89+
assert.Equal(t, []int32{0}, record.AttributeIndices().AsRaw())
90+
91+
// Special case: removing and adding again should not change the table as
92+
// this can lead to multiple identical attributes in the table.
93+
record.AttributeIndices().FromRaw([]int32{})
94+
require.NoError(t, PutAttribute(table, record, "hello", pcommon.NewValueStr("world")))
95+
assert.Equal(t, 1, table.Len())
96+
assert.Equal(t, []int32{0}, record.AttributeIndices().AsRaw())
97+
98+
// Put an attribute, same key, different value.
99+
// This updates the index and adds to the table.
100+
fmt.Printf("test\n")
101+
require.NoError(t, PutAttribute(table, record, "hello", pcommon.NewValueStr("world2")))
58102
assert.Equal(t, 2, table.Len())
59-
assert.Equal(t, []int32{1}, loc.AttributeIndices().AsRaw())
60-
61-
// Add an already existing attribute
62-
mapp := NewMapping()
63-
err = AddAttribute(table, mapp, "hello", pcommon.NewValueStr("world"))
64-
require.NoError(t, err)
103+
assert.Equal(t, []int32{1}, record.AttributeIndices().AsRaw())
65104

105+
// Put an attribute that already exists in the table.
106+
// This updates the index and does not add to the table.
107+
require.NoError(t, PutAttribute(table, record, "hello", pcommon.NewValueStr("world")))
66108
assert.Equal(t, 2, table.Len())
67-
assert.Equal(t, []int32{0}, mapp.AttributeIndices().AsRaw())
109+
assert.Equal(t, []int32{0}, record.AttributeIndices().AsRaw())
110+
111+
// Put a new attribute.
112+
// This adds an index and adds to the table.
113+
require.NoError(t, PutAttribute(table, record, "good", pcommon.NewValueStr("day")))
114+
assert.Equal(t, 3, table.Len())
115+
assert.Equal(t, []int32{0, 2}, record.AttributeIndices().AsRaw())
116+
117+
// Add multiple distinct attributes.
118+
for i := range 100 {
119+
require.NoError(t, PutAttribute(table, record, fmt.Sprintf("key_%d", i), pcommon.NewValueStr("day")))
120+
assert.Equal(t, i+4, table.Len())
121+
assert.Equal(t, i+3, record.AttributeIndices().Len())
122+
}
68123

69-
// Add a duplicate attribute
70-
err = AddAttribute(table, mapp, "hello", pcommon.NewValueStr("world"))
71-
require.NoError(t, err)
124+
// Add a negative index to the record.
125+
record.AttributeIndices().Append(-1)
126+
tableLen := table.Len()
127+
indicesLen := record.AttributeIndices().Len()
128+
// Try putting a new attribute, make sure it fails, and that table/indices didn't change.
129+
require.Error(t, PutAttribute(table, record, "newKey", pcommon.NewValueStr("value")))
130+
require.Equal(t, tableLen, table.Len())
131+
require.Equal(t, indicesLen, record.AttributeIndices().Len())
132+
133+
// Set the last index to the table length, which is out of range.
134+
record.AttributeIndices().SetAt(indicesLen-1, int32(tableLen)) //nolint:gosec
135+
// Try putting a new attribute, make sure it fails, and that table/indices didn't change.
136+
require.Error(t, PutAttribute(table, record, "newKey", pcommon.NewValueStr("value")))
137+
require.Equal(t, tableLen, table.Len())
138+
require.Equal(t, indicesLen, record.AttributeIndices().Len())
139+
}
72140

73-
assert.Equal(t, 2, table.Len())
74-
assert.Equal(t, []int32{0}, mapp.AttributeIndices().AsRaw())
141+
func TestPutAttribute(t *testing.T) {
142+
// Test every existing record type.
143+
for _, tt := range []struct {
144+
name string
145+
attr attributable
146+
}{
147+
{"Profile", NewProfile()},
148+
{"Sample", NewSample()},
149+
{"Mapping", NewMapping()},
150+
{"Location", NewLocation()},
151+
} {
152+
t.Run(tt.name, func(t *testing.T) {
153+
testPutAttribute(t, tt.attr)
154+
})
155+
}
75156
}
76157

77158
func BenchmarkFromAttributeIndices(b *testing.B) {
@@ -94,7 +175,7 @@ func BenchmarkFromAttributeIndices(b *testing.B) {
94175
}
95176
}
96177

97-
func BenchmarkAddAttribute(b *testing.B) {
178+
func BenchmarkPutAttribute(b *testing.B) {
98179
for _, bb := range []struct {
99180
name string
100181
key string
@@ -124,7 +205,7 @@ func BenchmarkAddAttribute(b *testing.B) {
124205
value: pcommon.NewValueStr("test"),
125206

126207
runBefore: func(_ *testing.B, table AttributeTableSlice, obj attributable) {
127-
require.NoError(b, AddAttribute(table, obj, "attribute", pcommon.NewValueStr("test")))
208+
require.NoError(b, PutAttribute(table, obj, "attribute", pcommon.NewValueStr("test")))
128209
},
129210
},
130211
{
@@ -157,7 +238,7 @@ func BenchmarkAddAttribute(b *testing.B) {
157238
b.ReportAllocs()
158239

159240
for n := 0; n < b.N; n++ {
160-
_ = AddAttribute(table, obj, bb.key, bb.value)
241+
_ = PutAttribute(table, obj, bb.key, bb.value)
161242
}
162243
})
163244
}

0 commit comments

Comments
 (0)