Skip to content
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

introduce pprofile.AddAttribute helper #12390

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Next Next commit
introduce pprofile.AddAttribute helper
  • Loading branch information
dmathieu committed Feb 14, 2025
commit 1c1404fff272922421e26ab0b1cfc921d1a64d28
36 changes: 36 additions & 0 deletions pdata/pprofile/attributes.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
package pprofile // import "go.opentelemetry.io/collector/pdata/pprofile"

import (
"errors"
"fmt"
"math"

"go.opentelemetry.io/collector/pdata/pcommon"
)

Expand All @@ -26,3 +30,35 @@ func FromAttributeIndices(table AttributeTableSlice, record attributable) pcommo

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 any) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How come the attributable type is not an exported type (of some package)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

attributable is there to allow the behavior on any of the generated types that have attribute indices.
Location, Mapping, Sample.
Folks will only ever need to manipulate the generated struct. They don't need the interface.

for i := 0; i < table.Len(); i++ {
a := table.At(i)

if a.Key() == key && value == a.Value().AsRaw() {
if i >= math.MaxInt32 {
return fmt.Errorf("Attribute %s=%#v has too high an indice to be added to AttributeIndices", key, value)
}

record.AttributeIndices().Append(int32(i)) //nolint:gosec // overflow checked
return nil
}
}

if table.Len() >= math.MaxInt32 {
return errors.New("AttributeTable can't take more attributes")
}
table.EnsureCapacity(table.Len() + 1)
entry := table.AppendEmpty()
entry.SetKey(key)
err := entry.Value().FromRaw(value)
if err != nil {
return err
}
record.AttributeIndices().Append(int32(table.Len()) - 1) //nolint:gosec // overflow checked

return nil
}
84 changes: 84 additions & 0 deletions pdata/pprofile/attributes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"go.opentelemetry.io/collector/pdata/pcommon"
)
Expand Down Expand Up @@ -43,6 +44,29 @@ func TestFromAttributeIndices(t *testing.T) {
assert.Equal(t, attrs.AsRaw(), m)
}

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

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

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", "world")
require.NoError(t, err)

assert.Equal(t, 2, table.Len())
assert.Equal(t, []int32{0}, mapp.AttributeIndices().AsRaw())
}

func BenchmarkFromAttributeIndices(b *testing.B) {
table := NewAttributeTableSlice()

Expand All @@ -62,3 +86,63 @@ func BenchmarkFromAttributeIndices(b *testing.B) {
_ = FromAttributeIndices(table, obj)
}
}

func BenchmarkAddAttribute(b *testing.B) {
for _, bb := range []struct {
name string
key string
value any

runBefore func(*testing.B, AttributeTableSlice)
}{
{
name: "with a new string attribute",
key: "attribute",
value: "test",
},
{
name: "with an existing attribute",
key: "attribute",
value: "test",

runBefore: func(_ *testing.B, table AttributeTableSlice) {
entry := table.AppendEmpty()
entry.SetKey("attribute")
entry.Value().SetStr("test")
},
},
{
name: "with a hundred attributes to loop through",
key: "attribute",
value: "test",

runBefore: func(_ *testing.B, table AttributeTableSlice) {
for i := range 100 {
entry := table.AppendEmpty()
entry.SetKey(fmt.Sprintf("attr_%d", i))
entry.Value().SetStr("test")
}

entry := table.AppendEmpty()
entry.SetKey("attribute")
entry.Value().SetStr("test")
},
},
} {
b.Run(bb.name, func(b *testing.B) {
table := NewAttributeTableSlice()
obj := NewLocation()

if bb.runBefore != nil {
bb.runBefore(b, table)
}

b.ResetTimer()
b.ReportAllocs()

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