Skip to content

Commit

Permalink
feat(xattribute): add utility package for building attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
tdakkota committed May 28, 2024
1 parent b6b266e commit 16c696d
Show file tree
Hide file tree
Showing 3 changed files with 179 additions and 0 deletions.
29 changes: 29 additions & 0 deletions internal/xattribute/pool.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package xattribute

import "sync"

type stringSlice struct {
val []string
}

func (s *stringSlice) Reset() {
s.val = s.val[:0]
}

var stringSlicePool = &sync.Pool{
New: func() any {
return &stringSlice{
val: make([]string, 0, 16),
}
},
}

func getStringSlice() *stringSlice {
ss := stringSlicePool.Get().(*stringSlice)
ss.Reset()
return ss
}

func putStringSlice(ss *stringSlice) {
stringSlicePool.Put(ss)
}
53 changes: 53 additions & 0 deletions internal/xattribute/xattribute.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Package xattribute provides some helpers to create OpenTelemetry attributes.
package xattribute

import (
"fmt"
"slices"

"go.opentelemetry.io/otel/attribute"
)

// StringerSlice creates a string slice attribute from slice of [fmt.Stringer] implementations.
func StringerSlice[S ~[]E, E fmt.Stringer](k string, v S) attribute.KeyValue {
if len(v) == 0 {
return attribute.StringSlice(k, nil)
}

// Using pooled string slice is fine, since attribute package copies slice.
ss := getStringSlice()
defer putStringSlice(ss)

ss.val = append(ss.val, make([]string, len(v))...)
for i, f := range v {
ss.val[i] = safeStringer(f)
}
return attribute.StringSlice(k, ss.val)
}

func safeStringer[F fmt.Stringer](f F) (v string) {
defer func() {
if r := recover(); r != nil {
v = "<stringer panic>:" + fmt.Sprint(r)
}
}()
return f.String()
}

// StringMap creates a sorted string slice attribute from string map.
func StringMap(k string, m map[string]string) attribute.KeyValue {
if len(m) == 0 {
return attribute.StringSlice(k, nil)
}

// Using pooled string slice is fine, since attribute package copies slice.
ss := getStringSlice()
defer putStringSlice(ss)

for k, v := range m {
ss.val = append(ss.val, k+"="+v)
}
slices.Sort(ss.val)

return attribute.StringSlice(k, ss.val)
}
97 changes: 97 additions & 0 deletions internal/xattribute/xattribute_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package xattribute

import (
"fmt"
"runtime"
"testing"

"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/attribute"
)

type justStringer struct{}

func (justStringer) String() string {
return "justStringer"
}

type panicStringer struct{}

func (panicStringer) String() string {
panic("bad stringer")
}

func TestStringerSlice(t *testing.T) {
tests := []struct {
k string
v []fmt.Stringer
want attribute.KeyValue
}{
{
"key",
nil,
attribute.StringSlice("key", nil),
},
{
"key",
[]fmt.Stringer{justStringer{}},
attribute.StringSlice("key", []string{"justStringer"}),
},
{
"key",
[]fmt.Stringer{panicStringer{}},
attribute.StringSlice("key", []string{"<stringer panic>:bad stringer"}),
},
{
"key",
[]fmt.Stringer{nil},
attribute.StringSlice("key", []string{"<stringer panic>:runtime error: invalid memory address or nil pointer dereference"}),
},
}
for i, tt := range tests {
tt := tt
t.Run(fmt.Sprintf("Test%d", i+1), func(t *testing.T) {
require.Equal(t, tt.want, StringerSlice(tt.k, tt.v))
})
}
}

func BenchmarkStringerSlice(b *testing.B) {
var (
s [4]justStringer
sink attribute.KeyValue
)
b.ReportAllocs()
b.ResetTimer()

for i := 0; i < b.N; i++ {
sink = StringerSlice("key", s[:])
}

runtime.KeepAlive(sink)
}

func TestStringMap(t *testing.T) {
tests := []struct {
k string
m map[string]string
want attribute.KeyValue
}{
{
"key",
nil,
attribute.StringSlice("key", nil),
},
{
"key",
map[string]string{"a": "1", "b": "2"},
attribute.StringSlice("key", []string{"a=1", "b=2"}),
},
}
for i, tt := range tests {
tt := tt
t.Run(fmt.Sprintf("Test%d", i+1), func(t *testing.T) {
require.Equal(t, tt.want, StringMap(tt.k, tt.m))
})
}
}

0 comments on commit 16c696d

Please sign in to comment.