blob: 032c917e0cc12e96d47bc9d23c9fbe287274036e [file]
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package attribute_test
import (
"math"
"testing"
"go.opentelemetry.io/otel/attribute"
)
// Store results in a file scope var to ensure compiler does not optimize the
// test away.
var (
outV attribute.Value
outKV attribute.KeyValue
outBool bool
outBoolSlice []bool
outInt64 int64
outInt64Slice []int64
outFloat64 float64
outFloat64Slice []float64
outStr string
outStrSlice []string
outValueSlice []attribute.Value
outMap []attribute.KeyValue
)
func benchmarkEmit(kv attribute.KeyValue) func(*testing.B) {
return func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
outStr = kv.Value.Emit() //nolint:staticcheck // Benchmark the deprecated formatter for comparison.
}
}
}
func benchmarkValueString(v attribute.Value) func(*testing.B) {
return func(b *testing.B) {
b.ReportAllocs()
for b.Loop() {
v.String()
}
}
}
func benchmarkKeyValueString(kv attribute.KeyValue) func(*testing.B) {
return func(b *testing.B) {
b.ReportAllocs()
for b.Loop() {
kv.String()
}
}
}
func runStringBenchmarks(b *testing.B, kv attribute.KeyValue) {
b.Run("String", benchmarkValueString(kv.Value))
b.Run("KeyValueString", benchmarkKeyValueString(kv))
b.Run("Emit", benchmarkEmit(kv))
}
func BenchmarkBool(b *testing.B) {
k, v := "bool", true
kv := attribute.Bool(k, v)
b.Run("Value", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
outV = attribute.BoolValue(v)
}
})
b.Run("KeyValue", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
outKV = attribute.Bool(k, v)
}
})
b.Run("AsBool", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
outBool = kv.Value.AsBool()
}
})
runStringBenchmarks(b, kv)
}
func BenchmarkBoolSlice(b *testing.B) {
for _, bench := range []struct {
name string
v []bool
}{
{name: "Len2", v: []bool{true, false}},
{name: "Len8", v: []bool{true, false, true, false, true, false, true, false}},
} {
b.Run(bench.name, func(b *testing.B) {
k, v := "bool slice", bench.v
kv := attribute.BoolSlice(k, v)
b.Run("Value", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
outV = attribute.BoolSliceValue(v)
}
})
b.Run("KeyValue", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
outKV = attribute.BoolSlice(k, v)
}
})
b.Run("AsBoolSlice", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
outBoolSlice = kv.Value.AsBoolSlice()
}
})
runStringBenchmarks(b, kv)
})
}
}
func BenchmarkInt(b *testing.B) {
k, v := "int", int(42)
kv := attribute.Int(k, v)
b.Run("Value", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
outV = attribute.IntValue(v)
}
})
b.Run("KeyValue", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
outKV = attribute.Int(k, v)
}
})
runStringBenchmarks(b, kv)
}
func BenchmarkIntSlice(b *testing.B) {
for _, bench := range []struct {
name string
v []int
}{
{name: "Len2", v: []int{42, -3}},
{name: "Len8", v: []int{42, -3, 12, 7, 9, 11, -5, 0}},
} {
b.Run(bench.name, func(b *testing.B) {
k, v := "int slice", bench.v
kv := attribute.IntSlice(k, v)
b.Run("Value", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
outV = attribute.IntSliceValue(v)
}
})
b.Run("KeyValue", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
outKV = attribute.IntSlice(k, v)
}
})
runStringBenchmarks(b, kv)
})
}
}
func BenchmarkInt64(b *testing.B) {
k, v := "int64", int64(42)
kv := attribute.Int64(k, v)
b.Run("Value", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
outV = attribute.Int64Value(v)
}
})
b.Run("KeyValue", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
outKV = attribute.Int64(k, v)
}
})
b.Run("AsInt64", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
outInt64 = kv.Value.AsInt64()
}
})
runStringBenchmarks(b, kv)
}
func BenchmarkInt64Slice(b *testing.B) {
for _, bench := range []struct {
name string
v []int64
}{
{name: "Len2", v: []int64{42, -3}},
{name: "Len8", v: []int64{42, -3, 12, 7, 9, 11, -5, 0}},
} {
b.Run(bench.name, func(b *testing.B) {
k, v := "int64 slice", bench.v
kv := attribute.Int64Slice(k, v)
b.Run("Value", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
outV = attribute.Int64SliceValue(v)
}
})
b.Run("KeyValue", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
outKV = attribute.Int64Slice(k, v)
}
})
b.Run("AsInt64Slice", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
outInt64Slice = kv.Value.AsInt64Slice()
}
})
runStringBenchmarks(b, kv)
})
}
}
func BenchmarkFloat64(b *testing.B) {
k, v := "float64", float64(42)
kv := attribute.Float64(k, v)
b.Run("Value", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
outV = attribute.Float64Value(v)
}
})
b.Run("KeyValue", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
outKV = attribute.Float64(k, v)
}
})
b.Run("AsFloat64", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
outFloat64 = kv.Value.AsFloat64()
}
})
runStringBenchmarks(b, kv)
}
func BenchmarkFloat64Slice(b *testing.B) {
for _, bench := range []struct {
name string
v []float64
}{
{name: "Len2", v: []float64{42, -3}},
{name: "Len8", v: []float64{42, -3, 12, 7, 9, 11, -5, 0}},
} {
b.Run(bench.name, func(b *testing.B) {
k, v := "float64 slice", bench.v
kv := attribute.Float64Slice(k, v)
b.Run("Value", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
outV = attribute.Float64SliceValue(v)
}
})
b.Run("KeyValue", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
outKV = attribute.Float64Slice(k, v)
}
})
b.Run("AsFloat64Slice", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
outFloat64Slice = kv.Value.AsFloat64Slice()
}
})
runStringBenchmarks(b, kv)
})
}
}
func BenchmarkString(b *testing.B) {
k, v := "string", "42"
kv := attribute.String(k, v)
b.Run("Value", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
outV = attribute.StringValue(v)
}
})
b.Run("KeyValue", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
outKV = attribute.String(k, v)
}
})
b.Run("AsString", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
outStr = kv.Value.AsString()
}
})
runStringBenchmarks(b, kv)
}
func BenchmarkStringSlice(b *testing.B) {
for _, bench := range []struct {
name string
v []string
}{
{name: "Len2", v: []string{"forty-two", "negative three"}},
{name: "Len8", v: []string{"forty-two", "negative three", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen"}},
} {
b.Run(bench.name, func(b *testing.B) {
k, v := "string slice", bench.v
kv := attribute.StringSlice(k, v)
b.Run("Value", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
outV = attribute.StringSliceValue(v)
}
})
b.Run("KeyValue", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
outKV = attribute.StringSlice(k, v)
}
})
b.Run("AsStringSlice", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
outStrSlice = kv.Value.AsStringSlice()
}
})
runStringBenchmarks(b, kv)
})
}
}
func BenchmarkSlice(b *testing.B) {
for _, bench := range []struct {
name string
v []attribute.Value
}{
{
name: "Len3",
v: []attribute.Value{
attribute.BoolValue(true),
attribute.IntValue(42),
attribute.StringValue("test"),
},
},
{
name: "Len5Nested",
v: []attribute.Value{
attribute.StringValue("quote\""),
attribute.Float64Value(math.Inf(1)),
attribute.ByteSliceValue([]byte("bin")),
attribute.SliceValue(attribute.StringValue("nested"), attribute.Value{}),
attribute.BoolValue(false),
},
},
} {
b.Run(bench.name, func(b *testing.B) {
k, v := "slice", bench.v
kv := attribute.Slice(k, v...)
b.Run("Value", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
outV = attribute.SliceValue(v...)
}
})
b.Run("KeyValue", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
outKV = attribute.Slice(k, v...)
}
})
b.Run("AsSlice", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
outValueSlice = kv.Value.AsSlice()
}
})
runStringBenchmarks(b, kv)
})
}
}
func BenchmarkMap(b *testing.B) {
for _, bench := range []struct {
name string
v []attribute.KeyValue
}{
{
name: "Len3",
v: []attribute.KeyValue{
attribute.Bool("enabled", true),
attribute.Int("count", 42),
attribute.String("name", "test"),
},
},
{
name: "Len5Nested",
v: []attribute.KeyValue{
attribute.String("quote", "quote\""),
attribute.Float64("inf", math.Inf(1)),
attribute.ByteSlice("bin", []byte("bin")),
attribute.Key("nested").Map(attribute.String("key", "value"), attribute.Key("empty").Slice(attribute.Value{})),
attribute.Bool("enabled", false),
},
},
} {
b.Run(bench.name, func(b *testing.B) {
k, v := "map", bench.v
kv := attribute.Map(k, v...)
b.Run("Value", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
outV = attribute.MapValue(v...)
}
})
b.Run("KeyValue", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
outKV = attribute.Map(k, v...)
}
})
b.Run("AsMap", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
outMap = kv.Value.AsMap()
}
})
runStringBenchmarks(b, kv)
})
}
}
func BenchmarkByteSlice(b *testing.B) {
k, v := "bytes", []byte("forty-two")
kv := attribute.ByteSlice(k, v)
b.Run("Value", func(b *testing.B) {
b.ReportAllocs()
for b.Loop() {
attribute.ByteSliceValue(v)
}
})
b.Run("KeyValue", func(b *testing.B) {
b.ReportAllocs()
for b.Loop() {
attribute.ByteSlice(k, v)
}
})
b.Run("AsByteSlice", func(b *testing.B) {
b.ReportAllocs()
for b.Loop() {
kv.Value.AsByteSlice()
}
})
runStringBenchmarks(b, kv)
}
func BenchmarkSetEquals(b *testing.B) {
b.Run("Empty", func(b *testing.B) {
benchmarkSetEquals(b, attribute.EmptySet())
})
b.Run("1 string attribute", func(b *testing.B) {
set := attribute.NewSet(attribute.String("string", "42"))
benchmarkSetEquals(b, &set)
})
b.Run("10 string attributes", func(b *testing.B) {
set := attribute.NewSet(
attribute.String("a", "42"),
attribute.String("b", "42"),
attribute.String("c", "42"),
attribute.String("d", "42"),
attribute.String("e", "42"),
attribute.String("f", "42"),
attribute.String("g", "42"),
attribute.String("h", "42"),
attribute.String("i", "42"),
attribute.String("j", "42"),
)
benchmarkSetEquals(b, &set)
})
b.Run("1 int attribute", func(b *testing.B) {
set := attribute.NewSet(attribute.Int("string", 42))
benchmarkSetEquals(b, &set)
})
b.Run("10 int attributes", func(b *testing.B) {
set := attribute.NewSet(
attribute.Int("a", 42),
attribute.Int("b", 42),
attribute.Int("c", 42),
attribute.Int("d", 42),
attribute.Int("e", 42),
attribute.Int("f", 42),
attribute.Int("g", 42),
attribute.Int("h", 42),
attribute.Int("i", 42),
attribute.Int("j", 42),
)
benchmarkSetEquals(b, &set)
})
}
func benchmarkSetEquals(b *testing.B, set *attribute.Set) {
b.ResetTimer()
for range b.N {
if !set.Equals(set) {
b.Fatal("not equal")
}
}
}
// BenchmarkEquivalentMapAccess measures how expensive it is to use
// Equivalent() as a map key. This is on the hot path for making synchronous
// measurements on the metrics API/SDK. It will likely be on the hot path for
// the trace and logs API/SDK in the future.
func BenchmarkEquivalentMapAccess(b *testing.B) {
b.Run("Empty", func(b *testing.B) {
benchmarkEquivalentMapAccess(b, attribute.EmptySet())
})
b.Run("1 string attribute", func(b *testing.B) {
set := attribute.NewSet(attribute.String("string", "42"))
benchmarkEquivalentMapAccess(b, &set)
})
b.Run("10 string attributes", func(b *testing.B) {
set := attribute.NewSet(
attribute.String("a", "42"),
attribute.String("b", "42"),
attribute.String("c", "42"),
attribute.String("d", "42"),
attribute.String("e", "42"),
attribute.String("f", "42"),
attribute.String("g", "42"),
attribute.String("h", "42"),
attribute.String("i", "42"),
attribute.String("j", "42"),
)
benchmarkEquivalentMapAccess(b, &set)
})
b.Run("1 int attribute", func(b *testing.B) {
set := attribute.NewSet(attribute.Int("string", 42))
benchmarkEquivalentMapAccess(b, &set)
})
b.Run("10 int attributes", func(b *testing.B) {
set := attribute.NewSet(
attribute.Int("a", 42),
attribute.Int("b", 42),
attribute.Int("c", 42),
attribute.Int("d", 42),
attribute.Int("e", 42),
attribute.Int("f", 42),
attribute.Int("g", 42),
attribute.Int("h", 42),
attribute.Int("i", 42),
attribute.Int("j", 42),
)
benchmarkEquivalentMapAccess(b, &set)
})
}
func benchmarkEquivalentMapAccess(b *testing.B, set *attribute.Set) {
values := map[attribute.Distinct]int{}
b.ResetTimer()
for range b.N {
values[set.Equivalent()]++
}
}
func BenchmarkSetString(b *testing.B) {
for _, bench := range []struct {
name string
attrs []attribute.KeyValue
}{
{
name: "Len2",
attrs: []attribute.KeyValue{
attribute.String("a", "one"),
attribute.String("b", "two"),
},
},
{
name: "Len8",
attrs: []attribute.KeyValue{
attribute.String("a", "one"),
attribute.String("b", "two"),
attribute.String("c", "three"),
attribute.String("d", "four"),
attribute.String("e", "five"),
attribute.String("f", "six"),
attribute.String("g", "seven"),
attribute.String("h", "eight"),
},
},
} {
b.Run(bench.name, func(b *testing.B) {
set := attribute.NewSet(bench.attrs...)
b.ReportAllocs()
for b.Loop() {
set.String()
}
})
}
}