Skip to content

Introduce pprofile.PutAttribute helper #12798

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
25 changes: 25 additions & 0 deletions .chloggen/profiles-PutAttributes.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Use this changelog template to create an entry for release notes.

# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: enhancement

# The name of the component, or a single word describing the area of concern, (e.g. otlpreceiver)
component: pdata/profile

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Replace AddAttribute with the PutAttribute helper method to modify the content of attributable records.

# One or more tracking issues or pull requests related to the change
issues: [12798]

# (Optional) One or more lines of additional information to render under the primary note.
# These lines will be padded with 2 spaces and then inserted directly into the document.
# Use pipe (|) for multiline entries.
subtext:

# Optional: The change log or logs in which this entry should be included.
# e.g. '[user]' or '[user, api]'
# Include 'user' if the change is relevant to end users.
# Include 'api' if there is a change to a library API.
# Default: '[user]'
change_logs: [api]
73 changes: 57 additions & 16 deletions pdata/pprofile/attributes.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,38 +31,79 @@
return m
}

// AddAttribute updates an AttributeTable and a record's AttributeIndices to
// add a new attribute.
// The record can by any struct that implements an `AttributeIndices` method.
func AddAttribute(table AttributeTableSlice, record attributable, key string, value pcommon.Value) error {
for i := range table.Len() {
a := table.At(i)
var errTooManyTableEntries = errors.New("too many entries in AttributeTable")

if a.Key() == key && a.Value().Equal(value) {
if i >= math.MaxInt32 {
return fmt.Errorf("Attribute %s=%#v has too high an index to be added to AttributeIndices", key, value)
// PutAttribute updates an AttributeTable and a record's AttributeIndices to
// add a new attribute.
// The assumption is that attributes are a map as for other signals (metrics, logs, etc.), thus
// the same key must not appear twice in a list of attributes / attribute indices.
// The record can be any struct that implements an `AttributeIndices` method.
func PutAttribute(table AttributeTableSlice, record attributable, key string, value pcommon.Value) error {
for i := range record.AttributeIndices().Len() {
idx := int(record.AttributeIndices().At(i))
if idx < 0 || idx >= table.Len() {
return fmt.Errorf("index value %d out of range in AttributeIndices[%d]", idx, i)
}
attr := table.At(idx)
if attr.Key() == key {
if attr.Value().Equal(value) {
// Attribute already exists, nothing to do.
return nil
}

for j := range record.AttributeIndices().Len() {
v := record.AttributeIndices().At(j)
if v == int32(i) { //nolint:gosec // overflow checked
// If the attribute table already contains the key/value pair, just update the index.
for j := range table.Len() {
a := table.At(j)
if a.Key() == key && a.Value().Equal(value) {
if j > math.MaxInt32 {
return errTooManyTableEntries
}

Check warning on line 60 in pdata/pprofile/attributes.go

View check run for this annotation

Codecov / codecov/patch

pdata/pprofile/attributes.go#L59-L60

Added lines #L59 - L60 were not covered by tests
record.AttributeIndices().SetAt(i, int32(j)) //nolint:gosec // overflow checked
return nil
}
}

record.AttributeIndices().Append(int32(i)) //nolint:gosec // overflow checked
if table.Len() >= math.MaxInt32 {
return errTooManyTableEntries
}

Check warning on line 68 in pdata/pprofile/attributes.go

View check run for this annotation

Codecov / codecov/patch

pdata/pprofile/attributes.go#L67-L68

Added lines #L67 - L68 were not covered by tests

// Add the key/value pair as a new attribute to the table...
entry := table.AppendEmpty()
entry.SetKey(key)
value.CopyTo(entry.Value())

// ...and update the existing index.
record.AttributeIndices().SetAt(i, int32(table.Len()-1)) //nolint:gosec // overflow checked
return nil
}
}

if record.AttributeIndices().Len() >= math.MaxInt32 {
return errors.New("too many entries in AttributeIndices")
}

Check warning on line 83 in pdata/pprofile/attributes.go

View check run for this annotation

Codecov / codecov/patch

pdata/pprofile/attributes.go#L82-L83

Added lines #L82 - L83 were not covered by tests

for j := range table.Len() {
a := table.At(j)
if a.Key() == key && a.Value().Equal(value) {
if j > math.MaxInt32 {
return errTooManyTableEntries
}

Check warning on line 90 in pdata/pprofile/attributes.go

View check run for this annotation

Codecov / codecov/patch

pdata/pprofile/attributes.go#L89-L90

Added lines #L89 - L90 were not covered by tests
// Add the index of the existing attribute to the indices.
record.AttributeIndices().Append(int32(j)) //nolint:gosec // overflow checked
return nil
}
}

if table.Len() >= math.MaxInt32 {
return errors.New("AttributeTable can't take more attributes")
return errTooManyTableEntries

Check warning on line 98 in pdata/pprofile/attributes.go

View check run for this annotation

Codecov / codecov/patch

pdata/pprofile/attributes.go#L98

Added line #L98 was not covered by tests
}
table.EnsureCapacity(table.Len() + 1)

// Add the key/value pair as a new attribute to the table...
entry := table.AppendEmpty()
entry.SetKey(key)
value.CopyTo(entry.Value())
record.AttributeIndices().Append(int32(table.Len()) - 1) //nolint:gosec // overflow checked

// ...and add a new index to the indices.
record.AttributeIndices().Append(int32(table.Len() - 1)) //nolint:gosec // overflow checked
return nil
}
99 changes: 75 additions & 24 deletions pdata/pprofile/attributes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,34 +44,85 @@ func TestFromAttributeIndices(t *testing.T) {
assert.Equal(t, attrs.AsRaw(), m)
}

func TestAddAttribute(t *testing.T) {
func testPutAttribute(t *testing.T, record attributable) {
table := NewAttributeTableSlice()
att := table.AppendEmpty()
att.SetKey("hello")
att.Value().SetStr("world")

// Add a brand new attribute
loc := NewLocation()
err := AddAttribute(table, loc, "bonjour", pcommon.NewValueStr("monde"))
require.NoError(t, err)

// Put a first attribute.
require.NoError(t, PutAttribute(table, record, "hello", pcommon.NewValueStr("world")))
assert.Equal(t, 1, table.Len())
assert.Equal(t, []int32{0}, record.AttributeIndices().AsRaw())

// Put an attribute, same key, same value.
// This should be a no-op.
require.NoError(t, PutAttribute(table, record, "hello", pcommon.NewValueStr("world")))
assert.Equal(t, 1, table.Len())
assert.Equal(t, []int32{0}, record.AttributeIndices().AsRaw())

// Special case: removing and adding again should not change the table as
// this can lead to multiple identical attributes in the table.
record.AttributeIndices().FromRaw([]int32{})
require.NoError(t, PutAttribute(table, record, "hello", pcommon.NewValueStr("world")))
assert.Equal(t, 1, table.Len())
assert.Equal(t, []int32{0}, record.AttributeIndices().AsRaw())

// Put an attribute, same key, different value.
// This updates the index and adds to the table.
fmt.Printf("test\n")
require.NoError(t, PutAttribute(table, record, "hello", pcommon.NewValueStr("world2")))
assert.Equal(t, 2, table.Len())
assert.Equal(t, []int32{1}, loc.AttributeIndices().AsRaw())

// Add an already existing attribute
mapp := NewMapping()
err = AddAttribute(table, mapp, "hello", pcommon.NewValueStr("world"))
require.NoError(t, err)
assert.Equal(t, []int32{1}, record.AttributeIndices().AsRaw())

// Put an attribute that already exists in the table.
// This updates the index and does not add to the table.
require.NoError(t, PutAttribute(table, record, "hello", pcommon.NewValueStr("world")))
assert.Equal(t, 2, table.Len())
assert.Equal(t, []int32{0}, mapp.AttributeIndices().AsRaw())
assert.Equal(t, []int32{0}, record.AttributeIndices().AsRaw())

// Put a new attribute.
// This adds an index and adds to the table.
require.NoError(t, PutAttribute(table, record, "good", pcommon.NewValueStr("day")))
assert.Equal(t, 3, table.Len())
assert.Equal(t, []int32{0, 2}, record.AttributeIndices().AsRaw())

// Add multiple distinct attributes.
for i := range 100 {
require.NoError(t, PutAttribute(table, record, fmt.Sprintf("key_%d", i), pcommon.NewValueStr("day")))
assert.Equal(t, i+4, table.Len())
assert.Equal(t, i+3, record.AttributeIndices().Len())
}

// Add a duplicate attribute
err = AddAttribute(table, mapp, "hello", pcommon.NewValueStr("world"))
require.NoError(t, err)
// Add a negative index to the record.
record.AttributeIndices().Append(-1)
tableLen := table.Len()
indicesLen := record.AttributeIndices().Len()
// Try putting a new attribute, make sure it fails, and that table/indices didn't change.
require.Error(t, PutAttribute(table, record, "newKey", pcommon.NewValueStr("value")))
require.Equal(t, tableLen, table.Len())
require.Equal(t, indicesLen, record.AttributeIndices().Len())

// Set the last index to the table length, which is out of range.
record.AttributeIndices().SetAt(indicesLen-1, int32(tableLen)) //nolint:gosec
// Try putting a new attribute, make sure it fails, and that table/indices didn't change.
require.Error(t, PutAttribute(table, record, "newKey", pcommon.NewValueStr("value")))
require.Equal(t, tableLen, table.Len())
require.Equal(t, indicesLen, record.AttributeIndices().Len())
}

assert.Equal(t, 2, table.Len())
assert.Equal(t, []int32{0}, mapp.AttributeIndices().AsRaw())
func TestPutAttribute(t *testing.T) {
// Test every existing record type.
for _, tt := range []struct {
name string
attr attributable
}{
{"Profile", NewProfile()},
{"Sample", NewSample()},
{"Mapping", NewMapping()},
{"Location", NewLocation()},
} {
t.Run(tt.name, func(t *testing.T) {
testPutAttribute(t, tt.attr)
})
}
}

func BenchmarkFromAttributeIndices(b *testing.B) {
Expand All @@ -94,7 +145,7 @@ func BenchmarkFromAttributeIndices(b *testing.B) {
}
}

func BenchmarkAddAttribute(b *testing.B) {
func BenchmarkPutAttribute(b *testing.B) {
for _, bb := range []struct {
name string
key string
Expand Down Expand Up @@ -124,7 +175,7 @@ func BenchmarkAddAttribute(b *testing.B) {
value: pcommon.NewValueStr("test"),

runBefore: func(_ *testing.B, table AttributeTableSlice, obj attributable) {
require.NoError(b, AddAttribute(table, obj, "attribute", pcommon.NewValueStr("test")))
require.NoError(b, PutAttribute(table, obj, "attribute", pcommon.NewValueStr("test")))
},
},
{
Expand Down Expand Up @@ -157,7 +208,7 @@ func BenchmarkAddAttribute(b *testing.B) {
b.ReportAllocs()

for n := 0; n < b.N; n++ {
_ = AddAttribute(table, obj, bb.key, bb.value)
_ = PutAttribute(table, obj, bb.key, bb.value)
}
})
}
Expand Down
Loading