| // 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() | |
| } | |
| }) | |
| } | |
| } |