You've already forked opentelemetry-go
mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2025-08-10 22:31:50 +02:00
Rename otel/label -> otel/attribute (#1541)
* Rename otel/label -> otel/attr Leave the imported name alone, to avoid a large diff and conflicts * Better import comment * Update CHANGELOG.md Co-authored-by: Tyler Yahn <MrAlias@users.noreply.github.com> * otel/attr -> otel/attribute * Missed the changelog entry * Get rid of import renaming * Merge remaining conflicts Co-authored-by: Tyler Yahn <MrAlias@users.noreply.github.com> Co-authored-by: Anthony Mirabella <a9@aneurysm9.com>
This commit is contained in:
220
attribute/benchmark_test.go
Normal file
220
attribute/benchmark_test.go
Normal file
@@ -0,0 +1,220 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package attribute_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
)
|
||||
|
||||
type test struct{}
|
||||
|
||||
var (
|
||||
arrayVal = []string{"one", "two"}
|
||||
arrayKeyVal = attribute.Array("array", arrayVal)
|
||||
|
||||
boolVal = true
|
||||
boolKeyVal = attribute.Bool("bool", boolVal)
|
||||
|
||||
intVal = int(1)
|
||||
intKeyVal = attribute.Int("int", intVal)
|
||||
|
||||
int8Val = int8(1)
|
||||
int8KeyVal = attribute.Int("int8", int(int8Val))
|
||||
|
||||
int16Val = int16(1)
|
||||
int16KeyVal = attribute.Int("int16", int(int16Val))
|
||||
|
||||
int64Val = int64(1)
|
||||
int64KeyVal = attribute.Int64("int64", int64Val)
|
||||
|
||||
float64Val = float64(1.0)
|
||||
float64KeyVal = attribute.Float64("float64", float64Val)
|
||||
|
||||
stringVal = "string"
|
||||
stringKeyVal = attribute.String("string", stringVal)
|
||||
|
||||
bytesVal = []byte("bytes")
|
||||
structVal = test{}
|
||||
)
|
||||
|
||||
func BenchmarkArrayKey(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = attribute.Array("array", arrayVal)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkArrayKeyAny(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = attribute.Any("array", arrayVal)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBoolKey(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = attribute.Bool("bool", boolVal)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBoolKeyAny(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = attribute.Any("bool", boolVal)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIntKey(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = attribute.Int("int", intVal)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIntKeyAny(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = attribute.Any("int", intVal)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkInt8KeyAny(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = attribute.Any("int8", int8Val)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkInt16KeyAny(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = attribute.Any("int16", int16Val)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkInt64Key(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = attribute.Int64("int64", int64Val)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkInt64KeyAny(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = attribute.Any("int64", int64Val)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFloat64Key(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = attribute.Float64("float64", float64Val)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFloat64KeyAny(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = attribute.Any("float64", float64Val)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStringKey(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = attribute.String("string", stringVal)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStringKeyAny(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = attribute.Any("string", stringVal)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBytesKeyAny(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = attribute.Any("bytes", bytesVal)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStructKeyAny(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = attribute.Any("struct", structVal)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEmitArray(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = arrayKeyVal.Value.Emit()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEmitBool(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = boolKeyVal.Value.Emit()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEmitInt(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = intKeyVal.Value.Emit()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEmitInt8(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = int8KeyVal.Value.Emit()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEmitInt16(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = int16KeyVal.Value.Emit()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEmitInt64(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = int64KeyVal.Value.Emit()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEmitFloat64(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = float64KeyVal.Value.Emit()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEmitString(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = stringKeyVal.Value.Emit()
|
||||
}
|
||||
}
|
20
attribute/doc.go
Normal file
20
attribute/doc.go
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// package attribute provides key and value attributes.
|
||||
//
|
||||
// This package is currently in a pre-GA phase. Backwards incompatible changes
|
||||
// may be introduced in subsequent minor version releases as we work to track
|
||||
// the evolving OpenTelemetry specification and user feedback.
|
||||
package attribute // import "go.opentelemetry.io/otel/attribute"
|
150
attribute/encoder.go
Normal file
150
attribute/encoder.go
Normal file
@@ -0,0 +1,150 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package attribute // import "go.opentelemetry.io/otel/attribute"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type (
|
||||
// Encoder is a mechanism for serializing a label set into a
|
||||
// specific string representation that supports caching, to
|
||||
// avoid repeated serialization. An example could be an
|
||||
// exporter encoding the label set into a wire representation.
|
||||
Encoder interface {
|
||||
// Encode returns the serialized encoding of the label
|
||||
// set using its Iterator. This result may be cached
|
||||
// by a attribute.Set.
|
||||
Encode(iterator Iterator) string
|
||||
|
||||
// ID returns a value that is unique for each class of
|
||||
// label encoder. Label encoders allocate these using
|
||||
// `NewEncoderID`.
|
||||
ID() EncoderID
|
||||
}
|
||||
|
||||
// EncoderID is used to identify distinct Encoder
|
||||
// implementations, for caching encoded results.
|
||||
EncoderID struct {
|
||||
value uint64
|
||||
}
|
||||
|
||||
// defaultLabelEncoder uses a sync.Pool of buffers to reduce
|
||||
// the number of allocations used in encoding labels. This
|
||||
// implementation encodes a comma-separated list of key=value,
|
||||
// with '/'-escaping of '=', ',', and '\'.
|
||||
defaultLabelEncoder struct {
|
||||
// pool is a pool of labelset builders. The buffers in this
|
||||
// pool grow to a size that most label encodings will not
|
||||
// allocate new memory.
|
||||
pool sync.Pool // *bytes.Buffer
|
||||
}
|
||||
)
|
||||
|
||||
// escapeChar is used to ensure uniqueness of the label encoding where
|
||||
// keys or values contain either '=' or ','. Since there is no parser
|
||||
// needed for this encoding and its only requirement is to be unique,
|
||||
// this choice is arbitrary. Users will see these in some exporters
|
||||
// (e.g., stdout), so the backslash ('\') is used as a conventional choice.
|
||||
const escapeChar = '\\'
|
||||
|
||||
var (
|
||||
_ Encoder = &defaultLabelEncoder{}
|
||||
|
||||
// encoderIDCounter is for generating IDs for other label
|
||||
// encoders.
|
||||
encoderIDCounter uint64
|
||||
|
||||
defaultEncoderOnce sync.Once
|
||||
defaultEncoderID = NewEncoderID()
|
||||
defaultEncoderInstance *defaultLabelEncoder
|
||||
)
|
||||
|
||||
// NewEncoderID returns a unique label encoder ID. It should be
|
||||
// called once per each type of label encoder. Preferably in init() or
|
||||
// in var definition.
|
||||
func NewEncoderID() EncoderID {
|
||||
return EncoderID{value: atomic.AddUint64(&encoderIDCounter, 1)}
|
||||
}
|
||||
|
||||
// DefaultEncoder returns a label encoder that encodes labels
|
||||
// in such a way that each escaped label's key is followed by an equal
|
||||
// sign and then by an escaped label's value. All key-value pairs are
|
||||
// separated by a comma.
|
||||
//
|
||||
// Escaping is done by prepending a backslash before either a
|
||||
// backslash, equal sign or a comma.
|
||||
func DefaultEncoder() Encoder {
|
||||
defaultEncoderOnce.Do(func() {
|
||||
defaultEncoderInstance = &defaultLabelEncoder{
|
||||
pool: sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &bytes.Buffer{}
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
return defaultEncoderInstance
|
||||
}
|
||||
|
||||
// Encode is a part of an implementation of the LabelEncoder
|
||||
// interface.
|
||||
func (d *defaultLabelEncoder) Encode(iter Iterator) string {
|
||||
buf := d.pool.Get().(*bytes.Buffer)
|
||||
defer d.pool.Put(buf)
|
||||
buf.Reset()
|
||||
|
||||
for iter.Next() {
|
||||
i, keyValue := iter.IndexedLabel()
|
||||
if i > 0 {
|
||||
_, _ = buf.WriteRune(',')
|
||||
}
|
||||
copyAndEscape(buf, string(keyValue.Key))
|
||||
|
||||
_, _ = buf.WriteRune('=')
|
||||
|
||||
if keyValue.Value.Type() == STRING {
|
||||
copyAndEscape(buf, keyValue.Value.AsString())
|
||||
} else {
|
||||
_, _ = buf.WriteString(keyValue.Value.Emit())
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// ID is a part of an implementation of the LabelEncoder interface.
|
||||
func (*defaultLabelEncoder) ID() EncoderID {
|
||||
return defaultEncoderID
|
||||
}
|
||||
|
||||
// copyAndEscape escapes `=`, `,` and its own escape character (`\`),
|
||||
// making the default encoding unique.
|
||||
func copyAndEscape(buf *bytes.Buffer, val string) {
|
||||
for _, ch := range val {
|
||||
switch ch {
|
||||
case '=', ',', escapeChar:
|
||||
buf.WriteRune(escapeChar)
|
||||
}
|
||||
buf.WriteRune(ch)
|
||||
}
|
||||
}
|
||||
|
||||
// Valid returns true if this encoder ID was allocated by
|
||||
// `NewEncoderID`. Invalid encoder IDs will not be cached.
|
||||
func (id EncoderID) Valid() bool {
|
||||
return id.value != 0
|
||||
}
|
143
attribute/iterator.go
Normal file
143
attribute/iterator.go
Normal file
@@ -0,0 +1,143 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package attribute // import "go.opentelemetry.io/otel/attribute"
|
||||
|
||||
// Iterator allows iterating over the set of labels in order,
|
||||
// sorted by key.
|
||||
type Iterator struct {
|
||||
storage *Set
|
||||
idx int
|
||||
}
|
||||
|
||||
// MergeIterator supports iterating over two sets of labels while
|
||||
// eliminating duplicate values from the combined set. The first
|
||||
// iterator value takes precedence.
|
||||
type MergeIterator struct {
|
||||
one oneIterator
|
||||
two oneIterator
|
||||
current KeyValue
|
||||
}
|
||||
|
||||
type oneIterator struct {
|
||||
iter Iterator
|
||||
done bool
|
||||
label KeyValue
|
||||
}
|
||||
|
||||
// Next moves the iterator to the next position. Returns false if there
|
||||
// are no more labels.
|
||||
func (i *Iterator) Next() bool {
|
||||
i.idx++
|
||||
return i.idx < i.Len()
|
||||
}
|
||||
|
||||
// Label returns current KeyValue. Must be called only after Next returns
|
||||
// true.
|
||||
func (i *Iterator) Label() KeyValue {
|
||||
kv, _ := i.storage.Get(i.idx)
|
||||
return kv
|
||||
}
|
||||
|
||||
// Attribute is a synonym for Label().
|
||||
func (i *Iterator) Attribute() KeyValue {
|
||||
return i.Label()
|
||||
}
|
||||
|
||||
// IndexedLabel returns current index and attribute. Must be called only
|
||||
// after Next returns true.
|
||||
func (i *Iterator) IndexedLabel() (int, KeyValue) {
|
||||
return i.idx, i.Label()
|
||||
}
|
||||
|
||||
// Len returns a number of labels in the iterator's `*Set`.
|
||||
func (i *Iterator) Len() int {
|
||||
return i.storage.Len()
|
||||
}
|
||||
|
||||
// ToSlice is a convenience function that creates a slice of labels
|
||||
// from the passed iterator. The iterator is set up to start from the
|
||||
// beginning before creating the slice.
|
||||
func (i *Iterator) ToSlice() []KeyValue {
|
||||
l := i.Len()
|
||||
if l == 0 {
|
||||
return nil
|
||||
}
|
||||
i.idx = -1
|
||||
slice := make([]KeyValue, 0, l)
|
||||
for i.Next() {
|
||||
slice = append(slice, i.Label())
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
||||
// NewMergeIterator returns a MergeIterator for merging two label sets
|
||||
// Duplicates are resolved by taking the value from the first set.
|
||||
func NewMergeIterator(s1, s2 *Set) MergeIterator {
|
||||
mi := MergeIterator{
|
||||
one: makeOne(s1.Iter()),
|
||||
two: makeOne(s2.Iter()),
|
||||
}
|
||||
return mi
|
||||
}
|
||||
|
||||
func makeOne(iter Iterator) oneIterator {
|
||||
oi := oneIterator{
|
||||
iter: iter,
|
||||
}
|
||||
oi.advance()
|
||||
return oi
|
||||
}
|
||||
|
||||
func (oi *oneIterator) advance() {
|
||||
if oi.done = !oi.iter.Next(); !oi.done {
|
||||
oi.label = oi.iter.Label()
|
||||
}
|
||||
}
|
||||
|
||||
// Next returns true if there is another label available.
|
||||
func (m *MergeIterator) Next() bool {
|
||||
if m.one.done && m.two.done {
|
||||
return false
|
||||
}
|
||||
if m.one.done {
|
||||
m.current = m.two.label
|
||||
m.two.advance()
|
||||
return true
|
||||
}
|
||||
if m.two.done {
|
||||
m.current = m.one.label
|
||||
m.one.advance()
|
||||
return true
|
||||
}
|
||||
if m.one.label.Key == m.two.label.Key {
|
||||
m.current = m.one.label // first iterator label value wins
|
||||
m.one.advance()
|
||||
m.two.advance()
|
||||
return true
|
||||
}
|
||||
if m.one.label.Key < m.two.label.Key {
|
||||
m.current = m.one.label
|
||||
m.one.advance()
|
||||
return true
|
||||
}
|
||||
m.current = m.two.label
|
||||
m.two.advance()
|
||||
return true
|
||||
}
|
||||
|
||||
// Label returns the current value after Next() returns true.
|
||||
func (m *MergeIterator) Label() KeyValue {
|
||||
return m.current
|
||||
}
|
149
attribute/iterator_test.go
Normal file
149
attribute/iterator_test.go
Normal file
@@ -0,0 +1,149 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package attribute_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
)
|
||||
|
||||
func TestIterator(t *testing.T) {
|
||||
one := attribute.String("one", "1")
|
||||
two := attribute.Int("two", 2)
|
||||
lbl := attribute.NewSet(one, two)
|
||||
iter := lbl.Iter()
|
||||
require.Equal(t, 2, iter.Len())
|
||||
|
||||
require.True(t, iter.Next())
|
||||
require.Equal(t, one, iter.Label())
|
||||
idx, attr := iter.IndexedLabel()
|
||||
require.Equal(t, 0, idx)
|
||||
require.Equal(t, one, attr)
|
||||
require.Equal(t, 2, iter.Len())
|
||||
|
||||
require.True(t, iter.Next())
|
||||
require.Equal(t, two, iter.Label())
|
||||
idx, attr = iter.IndexedLabel()
|
||||
require.Equal(t, 1, idx)
|
||||
require.Equal(t, two, attr)
|
||||
require.Equal(t, 2, iter.Len())
|
||||
|
||||
require.False(t, iter.Next())
|
||||
require.Equal(t, 2, iter.Len())
|
||||
}
|
||||
|
||||
func TestEmptyIterator(t *testing.T) {
|
||||
lbl := attribute.NewSet()
|
||||
iter := lbl.Iter()
|
||||
require.Equal(t, 0, iter.Len())
|
||||
require.False(t, iter.Next())
|
||||
}
|
||||
|
||||
func TestMergedIterator(t *testing.T) {
|
||||
|
||||
type inputs struct {
|
||||
name string
|
||||
keys1 []string
|
||||
keys2 []string
|
||||
expect []string
|
||||
}
|
||||
|
||||
makeLabels := func(keys []string, num int) (result []attribute.KeyValue) {
|
||||
for _, k := range keys {
|
||||
result = append(result, attribute.Int(k, num))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
for _, input := range []inputs{
|
||||
{
|
||||
name: "one overlap",
|
||||
keys1: []string{"A", "B"},
|
||||
keys2: []string{"B", "C"},
|
||||
expect: []string{"A/1", "B/1", "C/2"},
|
||||
},
|
||||
{
|
||||
name: "reversed one overlap",
|
||||
keys1: []string{"B", "A"},
|
||||
keys2: []string{"C", "B"},
|
||||
expect: []string{"A/1", "B/1", "C/2"},
|
||||
},
|
||||
{
|
||||
name: "one empty",
|
||||
keys1: nil,
|
||||
keys2: []string{"C", "B"},
|
||||
expect: []string{"B/2", "C/2"},
|
||||
},
|
||||
{
|
||||
name: "two empty",
|
||||
keys1: []string{"C", "B"},
|
||||
keys2: nil,
|
||||
expect: []string{"B/1", "C/1"},
|
||||
},
|
||||
{
|
||||
name: "no overlap both",
|
||||
keys1: []string{"C"},
|
||||
keys2: []string{"B"},
|
||||
expect: []string{"B/2", "C/1"},
|
||||
},
|
||||
{
|
||||
name: "one empty single two",
|
||||
keys1: nil,
|
||||
keys2: []string{"B"},
|
||||
expect: []string{"B/2"},
|
||||
},
|
||||
{
|
||||
name: "two empty single one",
|
||||
keys1: []string{"A"},
|
||||
keys2: nil,
|
||||
expect: []string{"A/1"},
|
||||
},
|
||||
{
|
||||
name: "all empty",
|
||||
keys1: nil,
|
||||
keys2: nil,
|
||||
expect: nil,
|
||||
},
|
||||
{
|
||||
name: "full overlap",
|
||||
keys1: []string{"A", "B", "C", "D"},
|
||||
keys2: []string{"A", "B", "C", "D"},
|
||||
expect: []string{"A/1", "B/1", "C/1", "D/1"},
|
||||
},
|
||||
} {
|
||||
t.Run(input.name, func(t *testing.T) {
|
||||
labels1 := makeLabels(input.keys1, 1)
|
||||
labels2 := makeLabels(input.keys2, 2)
|
||||
|
||||
set1 := attribute.NewSet(labels1...)
|
||||
set2 := attribute.NewSet(labels2...)
|
||||
|
||||
merge := attribute.NewMergeIterator(&set1, &set2)
|
||||
|
||||
var result []string
|
||||
|
||||
for merge.Next() {
|
||||
label := merge.Label()
|
||||
result = append(result, fmt.Sprint(label.Key, "/", label.Value.Emit()))
|
||||
}
|
||||
|
||||
require.Equal(t, input.expect, result)
|
||||
})
|
||||
}
|
||||
}
|
102
attribute/key.go
Normal file
102
attribute/key.go
Normal file
@@ -0,0 +1,102 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package attribute // import "go.opentelemetry.io/otel/attribute"
|
||||
|
||||
// Key represents the key part in key-value pairs. It's a string. The
|
||||
// allowed character set in the key depends on the use of the key.
|
||||
type Key string
|
||||
|
||||
// Bool creates a KeyValue instance with a BOOL Value.
|
||||
//
|
||||
// If creating both key and a bool value at the same time, then
|
||||
// instead of calling Key(name).Bool(value) consider using a
|
||||
// convenience function provided by the api/key package -
|
||||
// key.Bool(name, value).
|
||||
func (k Key) Bool(v bool) KeyValue {
|
||||
return KeyValue{
|
||||
Key: k,
|
||||
Value: BoolValue(v),
|
||||
}
|
||||
}
|
||||
|
||||
// Int64 creates a KeyValue instance with an INT64 Value.
|
||||
//
|
||||
// If creating both key and an int64 value at the same time, then
|
||||
// instead of calling Key(name).Int64(value) consider using a
|
||||
// convenience function provided by the api/key package -
|
||||
// key.Int64(name, value).
|
||||
func (k Key) Int64(v int64) KeyValue {
|
||||
return KeyValue{
|
||||
Key: k,
|
||||
Value: Int64Value(v),
|
||||
}
|
||||
}
|
||||
|
||||
// Float64 creates a KeyValue instance with a FLOAT64 Value.
|
||||
//
|
||||
// If creating both key and a float64 value at the same time, then
|
||||
// instead of calling Key(name).Float64(value) consider using a
|
||||
// convenience function provided by the api/key package -
|
||||
// key.Float64(name, value).
|
||||
func (k Key) Float64(v float64) KeyValue {
|
||||
return KeyValue{
|
||||
Key: k,
|
||||
Value: Float64Value(v),
|
||||
}
|
||||
}
|
||||
|
||||
// String creates a KeyValue instance with a STRING Value.
|
||||
//
|
||||
// If creating both key and a string value at the same time, then
|
||||
// instead of calling Key(name).String(value) consider using a
|
||||
// convenience function provided by the api/key package -
|
||||
// key.String(name, value).
|
||||
func (k Key) String(v string) KeyValue {
|
||||
return KeyValue{
|
||||
Key: k,
|
||||
Value: StringValue(v),
|
||||
}
|
||||
}
|
||||
|
||||
// Int creates a KeyValue instance with an INT64 Value.
|
||||
//
|
||||
// If creating both key and an int value at the same time, then
|
||||
// instead of calling Key(name).Int(value) consider using a
|
||||
// convenience function provided by the api/key package -
|
||||
// key.Int(name, value).
|
||||
func (k Key) Int(v int) KeyValue {
|
||||
return KeyValue{
|
||||
Key: k,
|
||||
Value: IntValue(v),
|
||||
}
|
||||
}
|
||||
|
||||
// Defined returns true for non-empty keys.
|
||||
func (k Key) Defined() bool {
|
||||
return len(k) != 0
|
||||
}
|
||||
|
||||
// Array creates a KeyValue instance with a ARRAY Value.
|
||||
//
|
||||
// If creating both key and a array value at the same time, then
|
||||
// instead of calling Key(name).String(value) consider using a
|
||||
// convenience function provided by the api/key package -
|
||||
// key.Array(name, value).
|
||||
func (k Key) Array(v interface{}) KeyValue {
|
||||
return KeyValue{
|
||||
Key: k,
|
||||
Value: ArrayValue(v),
|
||||
}
|
||||
}
|
101
attribute/key_test.go
Normal file
101
attribute/key_test.go
Normal file
@@ -0,0 +1,101 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package attribute_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
)
|
||||
|
||||
func TestDefined(t *testing.T) {
|
||||
for _, testcase := range []struct {
|
||||
name string
|
||||
k attribute.Key
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "Key.Defined() returns true when len(v.Name) != 0",
|
||||
k: attribute.Key("foo"),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Key.Defined() returns false when len(v.Name) == 0",
|
||||
k: attribute.Key(""),
|
||||
want: false,
|
||||
},
|
||||
} {
|
||||
t.Run(testcase.name, func(t *testing.T) {
|
||||
//func (k attribute.Key) Defined() bool {
|
||||
have := testcase.k.Defined()
|
||||
if have != testcase.want {
|
||||
t.Errorf("Want: %v, but have: %v", testcase.want, have)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONValue(t *testing.T) {
|
||||
var kvs interface{} = [2]attribute.KeyValue{
|
||||
attribute.String("A", "B"),
|
||||
attribute.Int64("C", 1),
|
||||
}
|
||||
|
||||
data, err := json.Marshal(kvs)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t,
|
||||
`[{"Key":"A","Value":{"Type":"STRING","Value":"B"}},{"Key":"C","Value":{"Type":"INT64","Value":1}}]`,
|
||||
string(data))
|
||||
}
|
||||
|
||||
func TestEmit(t *testing.T) {
|
||||
for _, testcase := range []struct {
|
||||
name string
|
||||
v attribute.Value
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: `test Key.Emit() can emit a string representing self.BOOL`,
|
||||
v: attribute.BoolValue(true),
|
||||
want: "true",
|
||||
},
|
||||
{
|
||||
name: `test Key.Emit() can emit a string representing self.INT64`,
|
||||
v: attribute.Int64Value(42),
|
||||
want: "42",
|
||||
},
|
||||
{
|
||||
name: `test Key.Emit() can emit a string representing self.FLOAT64`,
|
||||
v: attribute.Float64Value(42.1),
|
||||
want: "42.1",
|
||||
},
|
||||
{
|
||||
name: `test Key.Emit() can emit a string representing self.STRING`,
|
||||
v: attribute.StringValue("foo"),
|
||||
want: "foo",
|
||||
},
|
||||
} {
|
||||
t.Run(testcase.name, func(t *testing.T) {
|
||||
//proto: func (v attribute.Value) Emit() string {
|
||||
have := testcase.v.Emit()
|
||||
if have != testcase.want {
|
||||
t.Errorf("Want: %s, but have: %s", testcase.want, have)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
103
attribute/kv.go
Normal file
103
attribute/kv.go
Normal file
@@ -0,0 +1,103 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package attribute // import "go.opentelemetry.io/otel/attribute"
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// KeyValue holds a key and value pair.
|
||||
type KeyValue struct {
|
||||
Key Key
|
||||
Value Value
|
||||
}
|
||||
|
||||
// Bool creates a new key-value pair with a passed name and a bool
|
||||
// value.
|
||||
func Bool(k string, v bool) KeyValue {
|
||||
return Key(k).Bool(v)
|
||||
}
|
||||
|
||||
// Int64 creates a new key-value pair with a passed name and an int64
|
||||
// value.
|
||||
func Int64(k string, v int64) KeyValue {
|
||||
return Key(k).Int64(v)
|
||||
}
|
||||
|
||||
// Float64 creates a new key-value pair with a passed name and a float64
|
||||
// value.
|
||||
func Float64(k string, v float64) KeyValue {
|
||||
return Key(k).Float64(v)
|
||||
}
|
||||
|
||||
// String creates a new key-value pair with a passed name and a string
|
||||
// value.
|
||||
func String(k, v string) KeyValue {
|
||||
return Key(k).String(v)
|
||||
}
|
||||
|
||||
// Stringer creates a new key-value pair with a passed name and a string
|
||||
// value generated by the passed Stringer interface.
|
||||
func Stringer(k string, v fmt.Stringer) KeyValue {
|
||||
return Key(k).String(v.String())
|
||||
}
|
||||
|
||||
// Int creates a new key-value pair instance with a passed name and
|
||||
// either an int32 or an int64 value, depending on whether the int
|
||||
// type is 32 or 64 bits wide.
|
||||
func Int(k string, v int) KeyValue {
|
||||
return Key(k).Int(v)
|
||||
}
|
||||
|
||||
// Array creates a new key-value pair with a passed name and a array.
|
||||
// Only arrays of primitive type are supported.
|
||||
func Array(k string, v interface{}) KeyValue {
|
||||
return Key(k).Array(v)
|
||||
}
|
||||
|
||||
// Any creates a new key-value pair instance with a passed name and
|
||||
// automatic type inference. This is slower, and not type-safe.
|
||||
func Any(k string, value interface{}) KeyValue {
|
||||
if value == nil {
|
||||
return String(k, "<nil>")
|
||||
}
|
||||
|
||||
if stringer, ok := value.(fmt.Stringer); ok {
|
||||
return String(k, stringer.String())
|
||||
}
|
||||
|
||||
rv := reflect.ValueOf(value)
|
||||
|
||||
switch rv.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
return Array(k, value)
|
||||
case reflect.Bool:
|
||||
return Bool(k, rv.Bool())
|
||||
case reflect.Int, reflect.Int8, reflect.Int16:
|
||||
return Int(k, int(rv.Int()))
|
||||
case reflect.Int64:
|
||||
return Int64(k, rv.Int())
|
||||
case reflect.Float64:
|
||||
return Float64(k, rv.Float())
|
||||
case reflect.String:
|
||||
return String(k, rv.String())
|
||||
}
|
||||
if b, err := json.Marshal(value); b != nil && err == nil {
|
||||
return String(k, string(b))
|
||||
}
|
||||
return String(k, fmt.Sprint(value))
|
||||
}
|
162
attribute/kv_test.go
Normal file
162
attribute/kv_test.go
Normal file
@@ -0,0 +1,162 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package attribute_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
)
|
||||
|
||||
func TestKeyValueConstructors(t *testing.T) {
|
||||
tt := []struct {
|
||||
name string
|
||||
actual attribute.KeyValue
|
||||
expected attribute.KeyValue
|
||||
}{
|
||||
{
|
||||
name: "Bool",
|
||||
actual: attribute.Bool("k1", true),
|
||||
expected: attribute.KeyValue{
|
||||
Key: "k1",
|
||||
Value: attribute.BoolValue(true),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Int64",
|
||||
actual: attribute.Int64("k1", 123),
|
||||
expected: attribute.KeyValue{
|
||||
Key: "k1",
|
||||
Value: attribute.Int64Value(123),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Float64",
|
||||
actual: attribute.Float64("k1", 123.5),
|
||||
expected: attribute.KeyValue{
|
||||
Key: "k1",
|
||||
Value: attribute.Float64Value(123.5),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "String",
|
||||
actual: attribute.String("k1", "123.5"),
|
||||
expected: attribute.KeyValue{
|
||||
Key: "k1",
|
||||
Value: attribute.StringValue("123.5"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Int",
|
||||
actual: attribute.Int("k1", 123),
|
||||
expected: attribute.KeyValue{
|
||||
Key: "k1",
|
||||
Value: attribute.IntValue(123),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tt {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
if diff := cmp.Diff(test.actual, test.expected, cmp.AllowUnexported(attribute.Value{})); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAny(t *testing.T) {
|
||||
builder := &strings.Builder{}
|
||||
builder.WriteString("foo")
|
||||
jsonifyStruct := struct {
|
||||
Public string
|
||||
private string
|
||||
Tagged string `json:"tagName"`
|
||||
Empty string
|
||||
OmitEmpty string `json:",omitempty"`
|
||||
Omit string `json:"-"`
|
||||
}{"foo", "bar", "baz", "", "", "omitted"}
|
||||
invalidStruct := struct {
|
||||
N complex64
|
||||
}{complex(0, 0)}
|
||||
for _, testcase := range []struct {
|
||||
key string
|
||||
value interface{}
|
||||
wantType attribute.Type
|
||||
wantValue interface{}
|
||||
}{
|
||||
{
|
||||
key: "bool type inferred",
|
||||
value: true,
|
||||
wantType: attribute.BOOL,
|
||||
wantValue: true,
|
||||
},
|
||||
{
|
||||
key: "int64 type inferred",
|
||||
value: int64(42),
|
||||
wantType: attribute.INT64,
|
||||
wantValue: int64(42),
|
||||
},
|
||||
{
|
||||
key: "float64 type inferred",
|
||||
value: float64(42.1),
|
||||
wantType: attribute.FLOAT64,
|
||||
wantValue: 42.1,
|
||||
},
|
||||
{
|
||||
key: "string type inferred",
|
||||
value: "foo",
|
||||
wantType: attribute.STRING,
|
||||
wantValue: "foo",
|
||||
},
|
||||
{
|
||||
key: "stringer type inferred",
|
||||
value: builder,
|
||||
wantType: attribute.STRING,
|
||||
wantValue: "foo",
|
||||
},
|
||||
{
|
||||
key: "unknown value serialized as %v",
|
||||
value: nil,
|
||||
wantType: attribute.STRING,
|
||||
wantValue: "<nil>",
|
||||
},
|
||||
{
|
||||
key: "JSON struct serialized correctly",
|
||||
value: &jsonifyStruct,
|
||||
wantType: attribute.STRING,
|
||||
wantValue: `{"Public":"foo","tagName":"baz","Empty":""}`,
|
||||
},
|
||||
{
|
||||
key: "Invalid JSON struct falls back to string",
|
||||
value: &invalidStruct,
|
||||
wantType: attribute.STRING,
|
||||
wantValue: "&{(0+0i)}",
|
||||
},
|
||||
} {
|
||||
t.Logf("Running test case %s", testcase.key)
|
||||
keyValue := attribute.Any(testcase.key, testcase.value)
|
||||
if keyValue.Value.Type() != testcase.wantType {
|
||||
t.Errorf("wrong value type, got %#v, expected %#v", keyValue.Value.Type(), testcase.wantType)
|
||||
}
|
||||
got := keyValue.Value.AsInterface()
|
||||
if diff := cmp.Diff(testcase.wantValue, got); diff != "" {
|
||||
t.Errorf("+got, -want: %s", diff)
|
||||
}
|
||||
}
|
||||
}
|
471
attribute/set.go
Normal file
471
attribute/set.go
Normal file
@@ -0,0 +1,471 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package attribute // import "go.opentelemetry.io/otel/attribute"
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type (
|
||||
// Set is the representation for a distinct label set. It
|
||||
// manages an immutable set of labels, with an internal cache
|
||||
// for storing label encodings.
|
||||
//
|
||||
// This type supports the `Equivalent` method of comparison
|
||||
// using values of type `Distinct`.
|
||||
//
|
||||
// This type is used to implement:
|
||||
// 1. Metric labels
|
||||
// 2. Resource sets
|
||||
// 3. Correlation map (TODO)
|
||||
Set struct {
|
||||
equivalent Distinct
|
||||
|
||||
lock sync.Mutex
|
||||
encoders [maxConcurrentEncoders]EncoderID
|
||||
encoded [maxConcurrentEncoders]string
|
||||
}
|
||||
|
||||
// Distinct wraps a variable-size array of `KeyValue`,
|
||||
// constructed with keys in sorted order. This can be used as
|
||||
// a map key or for equality checking between Sets.
|
||||
Distinct struct {
|
||||
iface interface{}
|
||||
}
|
||||
|
||||
// Filter supports removing certain labels from label sets.
|
||||
// When the filter returns true, the label will be kept in
|
||||
// the filtered label set. When the filter returns false, the
|
||||
// label is excluded from the filtered label set, and the
|
||||
// label instead appears in the `removed` list of excluded labels.
|
||||
Filter func(KeyValue) bool
|
||||
|
||||
// Sortable implements `sort.Interface`, used for sorting
|
||||
// `KeyValue`. This is an exported type to support a
|
||||
// memory optimization. A pointer to one of these is needed
|
||||
// for the call to `sort.Stable()`, which the caller may
|
||||
// provide in order to avoid an allocation. See
|
||||
// `NewSetWithSortable()`.
|
||||
Sortable []KeyValue
|
||||
)
|
||||
|
||||
var (
|
||||
// keyValueType is used in `computeDistinctReflect`.
|
||||
keyValueType = reflect.TypeOf(KeyValue{})
|
||||
|
||||
// emptySet is returned for empty label sets.
|
||||
emptySet = &Set{
|
||||
equivalent: Distinct{
|
||||
iface: [0]KeyValue{},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const maxConcurrentEncoders = 3
|
||||
|
||||
// EmptySet returns a reference to a Set with no elements.
|
||||
//
|
||||
// This is a convenience provided for optimized calling utility.
|
||||
func EmptySet() *Set {
|
||||
return emptySet
|
||||
}
|
||||
|
||||
// reflect abbreviates `reflect.ValueOf`.
|
||||
func (d Distinct) reflect() reflect.Value {
|
||||
return reflect.ValueOf(d.iface)
|
||||
}
|
||||
|
||||
// Valid returns true if this value refers to a valid `*Set`.
|
||||
func (d Distinct) Valid() bool {
|
||||
return d.iface != nil
|
||||
}
|
||||
|
||||
// Len returns the number of labels in this set.
|
||||
func (l *Set) Len() int {
|
||||
if l == nil || !l.equivalent.Valid() {
|
||||
return 0
|
||||
}
|
||||
return l.equivalent.reflect().Len()
|
||||
}
|
||||
|
||||
// Get returns the KeyValue at ordered position `idx` in this set.
|
||||
func (l *Set) Get(idx int) (KeyValue, bool) {
|
||||
if l == nil {
|
||||
return KeyValue{}, false
|
||||
}
|
||||
value := l.equivalent.reflect()
|
||||
|
||||
if idx >= 0 && idx < value.Len() {
|
||||
// Note: The Go compiler successfully avoids an allocation for
|
||||
// the interface{} conversion here:
|
||||
return value.Index(idx).Interface().(KeyValue), true
|
||||
}
|
||||
|
||||
return KeyValue{}, false
|
||||
}
|
||||
|
||||
// Value returns the value of a specified key in this set.
|
||||
func (l *Set) Value(k Key) (Value, bool) {
|
||||
if l == nil {
|
||||
return Value{}, false
|
||||
}
|
||||
rValue := l.equivalent.reflect()
|
||||
vlen := rValue.Len()
|
||||
|
||||
idx := sort.Search(vlen, func(idx int) bool {
|
||||
return rValue.Index(idx).Interface().(KeyValue).Key >= k
|
||||
})
|
||||
if idx >= vlen {
|
||||
return Value{}, false
|
||||
}
|
||||
keyValue := rValue.Index(idx).Interface().(KeyValue)
|
||||
if k == keyValue.Key {
|
||||
return keyValue.Value, true
|
||||
}
|
||||
return Value{}, false
|
||||
}
|
||||
|
||||
// HasValue tests whether a key is defined in this set.
|
||||
func (l *Set) HasValue(k Key) bool {
|
||||
if l == nil {
|
||||
return false
|
||||
}
|
||||
_, ok := l.Value(k)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Iter returns an iterator for visiting the labels in this set.
|
||||
func (l *Set) Iter() Iterator {
|
||||
return Iterator{
|
||||
storage: l,
|
||||
idx: -1,
|
||||
}
|
||||
}
|
||||
|
||||
// ToSlice returns the set of labels belonging to this set, sorted,
|
||||
// where keys appear no more than once.
|
||||
func (l *Set) ToSlice() []KeyValue {
|
||||
iter := l.Iter()
|
||||
return iter.ToSlice()
|
||||
}
|
||||
|
||||
// Equivalent returns a value that may be used as a map key. The
|
||||
// Distinct type guarantees that the result will equal the equivalent
|
||||
// Distinct value of any label set with the same elements as this,
|
||||
// where sets are made unique by choosing the last value in the input
|
||||
// for any given key.
|
||||
func (l *Set) Equivalent() Distinct {
|
||||
if l == nil || !l.equivalent.Valid() {
|
||||
return emptySet.equivalent
|
||||
}
|
||||
return l.equivalent
|
||||
}
|
||||
|
||||
// Equals returns true if the argument set is equivalent to this set.
|
||||
func (l *Set) Equals(o *Set) bool {
|
||||
return l.Equivalent() == o.Equivalent()
|
||||
}
|
||||
|
||||
// Encoded returns the encoded form of this set, according to
|
||||
// `encoder`. The result will be cached in this `*Set`.
|
||||
func (l *Set) Encoded(encoder Encoder) string {
|
||||
if l == nil || encoder == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
id := encoder.ID()
|
||||
if !id.Valid() {
|
||||
// Invalid IDs are not cached.
|
||||
return encoder.Encode(l.Iter())
|
||||
}
|
||||
|
||||
var lookup *string
|
||||
l.lock.Lock()
|
||||
for idx := 0; idx < maxConcurrentEncoders; idx++ {
|
||||
if l.encoders[idx] == id {
|
||||
lookup = &l.encoded[idx]
|
||||
break
|
||||
}
|
||||
}
|
||||
l.lock.Unlock()
|
||||
|
||||
if lookup != nil {
|
||||
return *lookup
|
||||
}
|
||||
|
||||
r := encoder.Encode(l.Iter())
|
||||
|
||||
l.lock.Lock()
|
||||
defer l.lock.Unlock()
|
||||
|
||||
for idx := 0; idx < maxConcurrentEncoders; idx++ {
|
||||
if l.encoders[idx] == id {
|
||||
return l.encoded[idx]
|
||||
}
|
||||
if !l.encoders[idx].Valid() {
|
||||
l.encoders[idx] = id
|
||||
l.encoded[idx] = r
|
||||
return r
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This is a performance cliff. Find a way for this to
|
||||
// generate a warning.
|
||||
return r
|
||||
}
|
||||
|
||||
func empty() Set {
|
||||
return Set{
|
||||
equivalent: emptySet.equivalent,
|
||||
}
|
||||
}
|
||||
|
||||
// NewSet returns a new `Set`. See the documentation for
|
||||
// `NewSetWithSortableFiltered` for more details.
|
||||
//
|
||||
// Except for empty sets, this method adds an additional allocation
|
||||
// compared with calls that include a `*Sortable`.
|
||||
func NewSet(kvs ...KeyValue) Set {
|
||||
// Check for empty set.
|
||||
if len(kvs) == 0 {
|
||||
return empty()
|
||||
}
|
||||
s, _ := NewSetWithSortableFiltered(kvs, new(Sortable), nil)
|
||||
return s //nolint
|
||||
}
|
||||
|
||||
// NewSetWithSortable returns a new `Set`. See the documentation for
|
||||
// `NewSetWithSortableFiltered` for more details.
|
||||
//
|
||||
// This call includes a `*Sortable` option as a memory optimization.
|
||||
func NewSetWithSortable(kvs []KeyValue, tmp *Sortable) Set {
|
||||
// Check for empty set.
|
||||
if len(kvs) == 0 {
|
||||
return empty()
|
||||
}
|
||||
s, _ := NewSetWithSortableFiltered(kvs, tmp, nil)
|
||||
return s //nolint
|
||||
}
|
||||
|
||||
// NewSetWithFiltered returns a new `Set`. See the documentation for
|
||||
// `NewSetWithSortableFiltered` for more details.
|
||||
//
|
||||
// This call includes a `Filter` to include/exclude label keys from
|
||||
// the return value. Excluded keys are returned as a slice of label
|
||||
// values.
|
||||
func NewSetWithFiltered(kvs []KeyValue, filter Filter) (Set, []KeyValue) {
|
||||
// Check for empty set.
|
||||
if len(kvs) == 0 {
|
||||
return empty(), nil
|
||||
}
|
||||
return NewSetWithSortableFiltered(kvs, new(Sortable), filter)
|
||||
}
|
||||
|
||||
// NewSetWithSortableFiltered returns a new `Set`.
|
||||
//
|
||||
// Duplicate keys are eliminated by taking the last value. This
|
||||
// re-orders the input slice so that unique last-values are contiguous
|
||||
// at the end of the slice.
|
||||
//
|
||||
// This ensures the following:
|
||||
//
|
||||
// - Last-value-wins semantics
|
||||
// - Caller sees the reordering, but doesn't lose values
|
||||
// - Repeated call preserve last-value wins.
|
||||
//
|
||||
// Note that methods are defined on `*Set`, although this returns `Set`.
|
||||
// Callers can avoid memory allocations by:
|
||||
//
|
||||
// - allocating a `Sortable` for use as a temporary in this method
|
||||
// - allocating a `Set` for storing the return value of this
|
||||
// constructor.
|
||||
//
|
||||
// The result maintains a cache of encoded labels, by attribute.EncoderID.
|
||||
// This value should not be copied after its first use.
|
||||
//
|
||||
// The second `[]KeyValue` return value is a list of labels that were
|
||||
// excluded by the Filter (if non-nil).
|
||||
func NewSetWithSortableFiltered(kvs []KeyValue, tmp *Sortable, filter Filter) (Set, []KeyValue) {
|
||||
// Check for empty set.
|
||||
if len(kvs) == 0 {
|
||||
return empty(), nil
|
||||
}
|
||||
|
||||
*tmp = kvs
|
||||
|
||||
// Stable sort so the following de-duplication can implement
|
||||
// last-value-wins semantics.
|
||||
sort.Stable(tmp)
|
||||
|
||||
*tmp = nil
|
||||
|
||||
position := len(kvs) - 1
|
||||
offset := position - 1
|
||||
|
||||
// The requirements stated above require that the stable
|
||||
// result be placed in the end of the input slice, while
|
||||
// overwritten values are swapped to the beginning.
|
||||
//
|
||||
// De-duplicate with last-value-wins semantics. Preserve
|
||||
// duplicate values at the beginning of the input slice.
|
||||
for ; offset >= 0; offset-- {
|
||||
if kvs[offset].Key == kvs[position].Key {
|
||||
continue
|
||||
}
|
||||
position--
|
||||
kvs[offset], kvs[position] = kvs[position], kvs[offset]
|
||||
}
|
||||
if filter != nil {
|
||||
return filterSet(kvs[position:], filter)
|
||||
}
|
||||
return Set{
|
||||
equivalent: computeDistinct(kvs[position:]),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// filterSet reorders `kvs` so that included keys are contiguous at
|
||||
// the end of the slice, while excluded keys precede the included keys.
|
||||
func filterSet(kvs []KeyValue, filter Filter) (Set, []KeyValue) {
|
||||
var excluded []KeyValue
|
||||
|
||||
// Move labels that do not match the filter so
|
||||
// they're adjacent before calling computeDistinct().
|
||||
distinctPosition := len(kvs)
|
||||
|
||||
// Swap indistinct keys forward and distinct keys toward the
|
||||
// end of the slice.
|
||||
offset := len(kvs) - 1
|
||||
for ; offset >= 0; offset-- {
|
||||
if filter(kvs[offset]) {
|
||||
distinctPosition--
|
||||
kvs[offset], kvs[distinctPosition] = kvs[distinctPosition], kvs[offset]
|
||||
continue
|
||||
}
|
||||
}
|
||||
excluded = kvs[:distinctPosition]
|
||||
|
||||
return Set{
|
||||
equivalent: computeDistinct(kvs[distinctPosition:]),
|
||||
}, excluded
|
||||
}
|
||||
|
||||
// Filter returns a filtered copy of this `Set`. See the
|
||||
// documentation for `NewSetWithSortableFiltered` for more details.
|
||||
func (l *Set) Filter(re Filter) (Set, []KeyValue) {
|
||||
if re == nil {
|
||||
return Set{
|
||||
equivalent: l.equivalent,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Note: This could be refactored to avoid the temporary slice
|
||||
// allocation, if it proves to be expensive.
|
||||
return filterSet(l.ToSlice(), re)
|
||||
}
|
||||
|
||||
// computeDistinct returns a `Distinct` using either the fixed- or
|
||||
// reflect-oriented code path, depending on the size of the input.
|
||||
// The input slice is assumed to already be sorted and de-duplicated.
|
||||
func computeDistinct(kvs []KeyValue) Distinct {
|
||||
iface := computeDistinctFixed(kvs)
|
||||
if iface == nil {
|
||||
iface = computeDistinctReflect(kvs)
|
||||
}
|
||||
return Distinct{
|
||||
iface: iface,
|
||||
}
|
||||
}
|
||||
|
||||
// computeDistinctFixed computes a `Distinct` for small slices. It
|
||||
// returns nil if the input is too large for this code path.
|
||||
func computeDistinctFixed(kvs []KeyValue) interface{} {
|
||||
switch len(kvs) {
|
||||
case 1:
|
||||
ptr := new([1]KeyValue)
|
||||
copy((*ptr)[:], kvs)
|
||||
return *ptr
|
||||
case 2:
|
||||
ptr := new([2]KeyValue)
|
||||
copy((*ptr)[:], kvs)
|
||||
return *ptr
|
||||
case 3:
|
||||
ptr := new([3]KeyValue)
|
||||
copy((*ptr)[:], kvs)
|
||||
return *ptr
|
||||
case 4:
|
||||
ptr := new([4]KeyValue)
|
||||
copy((*ptr)[:], kvs)
|
||||
return *ptr
|
||||
case 5:
|
||||
ptr := new([5]KeyValue)
|
||||
copy((*ptr)[:], kvs)
|
||||
return *ptr
|
||||
case 6:
|
||||
ptr := new([6]KeyValue)
|
||||
copy((*ptr)[:], kvs)
|
||||
return *ptr
|
||||
case 7:
|
||||
ptr := new([7]KeyValue)
|
||||
copy((*ptr)[:], kvs)
|
||||
return *ptr
|
||||
case 8:
|
||||
ptr := new([8]KeyValue)
|
||||
copy((*ptr)[:], kvs)
|
||||
return *ptr
|
||||
case 9:
|
||||
ptr := new([9]KeyValue)
|
||||
copy((*ptr)[:], kvs)
|
||||
return *ptr
|
||||
case 10:
|
||||
ptr := new([10]KeyValue)
|
||||
copy((*ptr)[:], kvs)
|
||||
return *ptr
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// computeDistinctReflect computes a `Distinct` using reflection,
|
||||
// works for any size input.
|
||||
func computeDistinctReflect(kvs []KeyValue) interface{} {
|
||||
at := reflect.New(reflect.ArrayOf(len(kvs), keyValueType)).Elem()
|
||||
for i, keyValue := range kvs {
|
||||
*(at.Index(i).Addr().Interface().(*KeyValue)) = keyValue
|
||||
}
|
||||
return at.Interface()
|
||||
}
|
||||
|
||||
// MarshalJSON returns the JSON encoding of the `*Set`.
|
||||
func (l *Set) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(l.equivalent.iface)
|
||||
}
|
||||
|
||||
// Len implements `sort.Interface`.
|
||||
func (l *Sortable) Len() int {
|
||||
return len(*l)
|
||||
}
|
||||
|
||||
// Swap implements `sort.Interface`.
|
||||
func (l *Sortable) Swap(i, j int) {
|
||||
(*l)[i], (*l)[j] = (*l)[j], (*l)[i]
|
||||
}
|
||||
|
||||
// Less implements `sort.Interface`.
|
||||
func (l *Sortable) Less(i, j int) bool {
|
||||
return (*l)[i].Key < (*l)[j].Key
|
||||
}
|
190
attribute/set_test.go
Normal file
190
attribute/set_test.go
Normal file
@@ -0,0 +1,190 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package attribute_test
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
)
|
||||
|
||||
type testCase struct {
|
||||
kvs []attribute.KeyValue
|
||||
|
||||
keyRe *regexp.Regexp
|
||||
|
||||
encoding string
|
||||
fullEnc string
|
||||
}
|
||||
|
||||
func expect(enc string, kvs ...attribute.KeyValue) testCase {
|
||||
return testCase{
|
||||
kvs: kvs,
|
||||
encoding: enc,
|
||||
}
|
||||
}
|
||||
|
||||
func expectFiltered(enc, filter, fullEnc string, kvs ...attribute.KeyValue) testCase {
|
||||
return testCase{
|
||||
kvs: kvs,
|
||||
keyRe: regexp.MustCompile(filter),
|
||||
encoding: enc,
|
||||
fullEnc: fullEnc,
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetDedup(t *testing.T) {
|
||||
cases := []testCase{
|
||||
expect("A=B", attribute.String("A", "2"), attribute.String("A", "B")),
|
||||
expect("A=B", attribute.String("A", "2"), attribute.Int("A", 1), attribute.String("A", "B")),
|
||||
expect("A=B", attribute.String("A", "B"), attribute.String("A", "C"), attribute.String("A", "D"), attribute.String("A", "B")),
|
||||
|
||||
expect("A=B,C=D", attribute.String("A", "1"), attribute.String("C", "D"), attribute.String("A", "B")),
|
||||
expect("A=B,C=D", attribute.String("A", "2"), attribute.String("A", "B"), attribute.String("C", "D")),
|
||||
expect("A=B,C=D", attribute.Float64("C", 1.2), attribute.String("A", "2"), attribute.String("A", "B"), attribute.String("C", "D")),
|
||||
expect("A=B,C=D", attribute.String("C", "D"), attribute.String("A", "B"), attribute.String("A", "C"), attribute.String("A", "D"), attribute.String("A", "B")),
|
||||
expect("A=B,C=D", attribute.String("A", "B"), attribute.String("C", "D"), attribute.String("A", "C"), attribute.String("A", "D"), attribute.String("A", "B")),
|
||||
expect("A=B,C=D", attribute.String("A", "B"), attribute.String("A", "C"), attribute.String("A", "D"), attribute.String("A", "B"), attribute.String("C", "D")),
|
||||
}
|
||||
enc := attribute.DefaultEncoder()
|
||||
|
||||
s2d := map[string][]attribute.Distinct{}
|
||||
d2s := map[attribute.Distinct][]string{}
|
||||
|
||||
for _, tc := range cases {
|
||||
cpy := make([]attribute.KeyValue, len(tc.kvs))
|
||||
copy(cpy, tc.kvs)
|
||||
sl := attribute.NewSet(cpy...)
|
||||
|
||||
// Ensure that the input was reordered but no elements went missing.
|
||||
require.ElementsMatch(t, tc.kvs, cpy)
|
||||
|
||||
str := sl.Encoded(enc)
|
||||
equ := sl.Equivalent()
|
||||
|
||||
s2d[str] = append(s2d[str], equ)
|
||||
d2s[equ] = append(d2s[equ], str)
|
||||
|
||||
require.Equal(t, tc.encoding, str)
|
||||
}
|
||||
|
||||
for s, d := range s2d {
|
||||
// No other Distinct values are equal to this.
|
||||
for s2, d2 := range s2d {
|
||||
if s2 == s {
|
||||
continue
|
||||
}
|
||||
for _, elt := range d {
|
||||
for _, otherDistinct := range d2 {
|
||||
require.NotEqual(t, otherDistinct, elt)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, strings := range d2s {
|
||||
if strings[0] == s {
|
||||
continue
|
||||
}
|
||||
for _, otherString := range strings {
|
||||
require.NotEqual(t, otherString, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for d, s := range d2s {
|
||||
// No other Distinct values are equal to this.
|
||||
for d2, s2 := range d2s {
|
||||
if d2 == d {
|
||||
continue
|
||||
}
|
||||
for _, elt := range s {
|
||||
for _, otherDistinct := range s2 {
|
||||
require.NotEqual(t, otherDistinct, elt)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, distincts := range s2d {
|
||||
if distincts[0] == d {
|
||||
continue
|
||||
}
|
||||
for _, otherDistinct := range distincts {
|
||||
require.NotEqual(t, otherDistinct, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUniqueness(t *testing.T) {
|
||||
short := []attribute.KeyValue{
|
||||
attribute.String("A", "0"),
|
||||
attribute.String("B", "2"),
|
||||
attribute.String("A", "1"),
|
||||
}
|
||||
long := []attribute.KeyValue{
|
||||
attribute.String("B", "2"),
|
||||
attribute.String("C", "5"),
|
||||
attribute.String("B", "2"),
|
||||
attribute.String("C", "1"),
|
||||
attribute.String("A", "4"),
|
||||
attribute.String("C", "3"),
|
||||
attribute.String("A", "1"),
|
||||
}
|
||||
cases := []testCase{
|
||||
expectFiltered("A=1", "^A$", "B=2", short...),
|
||||
expectFiltered("B=2", "^B$", "A=1", short...),
|
||||
expectFiltered("A=1,B=2", "^A|B$", "", short...),
|
||||
expectFiltered("", "^C", "A=1,B=2", short...),
|
||||
|
||||
expectFiltered("A=1,C=3", "A|C", "B=2", long...),
|
||||
expectFiltered("B=2,C=3", "C|B", "A=1", long...),
|
||||
expectFiltered("C=3", "C", "A=1,B=2", long...),
|
||||
expectFiltered("", "D", "A=1,B=2,C=3", long...),
|
||||
}
|
||||
enc := attribute.DefaultEncoder()
|
||||
|
||||
for _, tc := range cases {
|
||||
cpy := make([]attribute.KeyValue, len(tc.kvs))
|
||||
copy(cpy, tc.kvs)
|
||||
distinct, uniq := attribute.NewSetWithFiltered(cpy, func(label attribute.KeyValue) bool {
|
||||
return tc.keyRe.MatchString(string(label.Key))
|
||||
})
|
||||
|
||||
full := attribute.NewSet(uniq...)
|
||||
|
||||
require.Equal(t, tc.encoding, distinct.Encoded(enc))
|
||||
require.Equal(t, tc.fullEnc, full.Encoded(enc))
|
||||
}
|
||||
}
|
||||
|
||||
func TestLookup(t *testing.T) {
|
||||
set := attribute.NewSet(attribute.Int("C", 3), attribute.Int("A", 1), attribute.Int("B", 2))
|
||||
|
||||
value, has := set.Value("C")
|
||||
require.True(t, has)
|
||||
require.Equal(t, int64(3), value.AsInt64())
|
||||
|
||||
value, has = set.Value("B")
|
||||
require.True(t, has)
|
||||
require.Equal(t, int64(2), value.AsInt64())
|
||||
|
||||
value, has = set.Value("A")
|
||||
require.True(t, has)
|
||||
require.Equal(t, int64(1), value.AsInt64())
|
||||
|
||||
value, has = set.Value("D")
|
||||
require.False(t, has)
|
||||
}
|
28
attribute/type_string.go
Normal file
28
attribute/type_string.go
Normal file
@@ -0,0 +1,28 @@
|
||||
// Code generated by "stringer -type=Type"; DO NOT EDIT.
|
||||
|
||||
package attribute
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[INVALID-0]
|
||||
_ = x[BOOL-1]
|
||||
_ = x[INT64-2]
|
||||
_ = x[FLOAT64-3]
|
||||
_ = x[STRING-4]
|
||||
_ = x[ARRAY-5]
|
||||
}
|
||||
|
||||
const _Type_name = "INVALIDBOOLINT64FLOAT64STRINGARRAY"
|
||||
|
||||
var _Type_index = [...]uint8{0, 7, 11, 16, 23, 29, 34}
|
||||
|
||||
func (i Type) String() string {
|
||||
if i < 0 || i >= Type(len(_Type_index)-1) {
|
||||
return "Type(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _Type_name[_Type_index[i]:_Type_index[i+1]]
|
||||
}
|
204
attribute/value.go
Normal file
204
attribute/value.go
Normal file
@@ -0,0 +1,204 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package attribute // import "go.opentelemetry.io/otel/attribute"
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"go.opentelemetry.io/otel/internal"
|
||||
)
|
||||
|
||||
//go:generate stringer -type=Type
|
||||
|
||||
// Type describes the type of the data Value holds.
|
||||
type Type int
|
||||
|
||||
// Value represents the value part in key-value pairs.
|
||||
type Value struct {
|
||||
vtype Type
|
||||
numeric uint64
|
||||
stringly string
|
||||
// TODO Lazy value type?
|
||||
|
||||
array interface{}
|
||||
}
|
||||
|
||||
const (
|
||||
// INVALID is used for a Value with no value set.
|
||||
INVALID Type = iota
|
||||
// BOOL is a boolean Type Value.
|
||||
BOOL
|
||||
// INT64 is a 64-bit signed integral Type Value.
|
||||
INT64
|
||||
// UINT32 is a 32-bit unsigned integral Type Value.
|
||||
FLOAT64
|
||||
// STRING is a string Type Value.
|
||||
STRING
|
||||
// ARRAY is an array Type Value used to store 1-dimensional slices or
|
||||
// arrays of bool, int, int32, int64, uint, uint32, uint64, float,
|
||||
// float32, float64, or string types.
|
||||
ARRAY
|
||||
)
|
||||
|
||||
// BoolValue creates a BOOL Value.
|
||||
func BoolValue(v bool) Value {
|
||||
return Value{
|
||||
vtype: BOOL,
|
||||
numeric: internal.BoolToRaw(v),
|
||||
}
|
||||
}
|
||||
|
||||
// Int64Value creates an INT64 Value.
|
||||
func Int64Value(v int64) Value {
|
||||
return Value{
|
||||
vtype: INT64,
|
||||
numeric: internal.Int64ToRaw(v),
|
||||
}
|
||||
}
|
||||
|
||||
// Float64Value creates a FLOAT64 Value.
|
||||
func Float64Value(v float64) Value {
|
||||
return Value{
|
||||
vtype: FLOAT64,
|
||||
numeric: internal.Float64ToRaw(v),
|
||||
}
|
||||
}
|
||||
|
||||
// StringValue creates a STRING Value.
|
||||
func StringValue(v string) Value {
|
||||
return Value{
|
||||
vtype: STRING,
|
||||
stringly: v,
|
||||
}
|
||||
}
|
||||
|
||||
// IntValue creates an INT64 Value.
|
||||
func IntValue(v int) Value {
|
||||
return Int64Value(int64(v))
|
||||
}
|
||||
|
||||
// ArrayValue creates an ARRAY value from an array or slice.
|
||||
// Only arrays or slices of bool, int, int64, float, float64, or string types are allowed.
|
||||
// Specifically, arrays and slices can not contain other arrays, slices, structs, or non-standard
|
||||
// types. If the passed value is not an array or slice of these types an
|
||||
// INVALID value is returned.
|
||||
func ArrayValue(v interface{}) Value {
|
||||
switch reflect.TypeOf(v).Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
// get array type regardless of dimensions
|
||||
typ := reflect.TypeOf(v).Elem()
|
||||
kind := typ.Kind()
|
||||
switch kind {
|
||||
case reflect.Bool, reflect.Int, reflect.Int64,
|
||||
reflect.Float64, reflect.String:
|
||||
val := reflect.ValueOf(v)
|
||||
length := val.Len()
|
||||
frozen := reflect.Indirect(reflect.New(reflect.ArrayOf(length, typ)))
|
||||
reflect.Copy(frozen, val)
|
||||
return Value{
|
||||
vtype: ARRAY,
|
||||
array: frozen.Interface(),
|
||||
}
|
||||
default:
|
||||
return Value{vtype: INVALID}
|
||||
}
|
||||
}
|
||||
return Value{vtype: INVALID}
|
||||
}
|
||||
|
||||
// Type returns a type of the Value.
|
||||
func (v Value) Type() Type {
|
||||
return v.vtype
|
||||
}
|
||||
|
||||
// AsBool returns the bool value. Make sure that the Value's type is
|
||||
// BOOL.
|
||||
func (v Value) AsBool() bool {
|
||||
return internal.RawToBool(v.numeric)
|
||||
}
|
||||
|
||||
// AsInt64 returns the int64 value. Make sure that the Value's type is
|
||||
// INT64.
|
||||
func (v Value) AsInt64() int64 {
|
||||
return internal.RawToInt64(v.numeric)
|
||||
}
|
||||
|
||||
// AsFloat64 returns the float64 value. Make sure that the Value's
|
||||
// type is FLOAT64.
|
||||
func (v Value) AsFloat64() float64 {
|
||||
return internal.RawToFloat64(v.numeric)
|
||||
}
|
||||
|
||||
// AsString returns the string value. Make sure that the Value's type
|
||||
// is STRING.
|
||||
func (v Value) AsString() string {
|
||||
return v.stringly
|
||||
}
|
||||
|
||||
// AsArray returns the array Value as an interface{}.
|
||||
func (v Value) AsArray() interface{} {
|
||||
return v.array
|
||||
}
|
||||
|
||||
type unknownValueType struct{}
|
||||
|
||||
// AsInterface returns Value's data as interface{}.
|
||||
func (v Value) AsInterface() interface{} {
|
||||
switch v.Type() {
|
||||
case ARRAY:
|
||||
return v.AsArray()
|
||||
case BOOL:
|
||||
return v.AsBool()
|
||||
case INT64:
|
||||
return v.AsInt64()
|
||||
case FLOAT64:
|
||||
return v.AsFloat64()
|
||||
case STRING:
|
||||
return v.stringly
|
||||
}
|
||||
return unknownValueType{}
|
||||
}
|
||||
|
||||
// Emit returns a string representation of Value's data.
|
||||
func (v Value) Emit() string {
|
||||
switch v.Type() {
|
||||
case ARRAY:
|
||||
return fmt.Sprint(v.array)
|
||||
case BOOL:
|
||||
return strconv.FormatBool(v.AsBool())
|
||||
case INT64:
|
||||
return strconv.FormatInt(v.AsInt64(), 10)
|
||||
case FLOAT64:
|
||||
return fmt.Sprint(v.AsFloat64())
|
||||
case STRING:
|
||||
return v.stringly
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalJSON returns the JSON encoding of the Value.
|
||||
func (v Value) MarshalJSON() ([]byte, error) {
|
||||
var jsonVal struct {
|
||||
Type string
|
||||
Value interface{}
|
||||
}
|
||||
jsonVal.Type = v.Type().String()
|
||||
jsonVal.Value = v.AsInterface()
|
||||
return json.Marshal(jsonVal)
|
||||
}
|
138
attribute/value_test.go
Normal file
138
attribute/value_test.go
Normal file
@@ -0,0 +1,138 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package attribute_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
)
|
||||
|
||||
func TestValue(t *testing.T) {
|
||||
k := attribute.Key("test")
|
||||
bli := getBitlessInfo(42)
|
||||
for _, testcase := range []struct {
|
||||
name string
|
||||
value attribute.Value
|
||||
wantType attribute.Type
|
||||
wantValue interface{}
|
||||
}{
|
||||
{
|
||||
name: "Key.Bool() correctly returns keys's internal bool value",
|
||||
value: k.Bool(true).Value,
|
||||
wantType: attribute.BOOL,
|
||||
wantValue: true,
|
||||
},
|
||||
{
|
||||
name: "Key.Array([]bool) correctly return key's internal bool values",
|
||||
value: k.Array([]bool{true, false}).Value,
|
||||
wantType: attribute.ARRAY,
|
||||
wantValue: [2]bool{true, false},
|
||||
},
|
||||
{
|
||||
name: "Key.Int64() correctly returns keys's internal int64 value",
|
||||
value: k.Int64(42).Value,
|
||||
wantType: attribute.INT64,
|
||||
wantValue: int64(42),
|
||||
},
|
||||
{
|
||||
name: "Key.Float64() correctly returns keys's internal float64 value",
|
||||
value: k.Float64(42.1).Value,
|
||||
wantType: attribute.FLOAT64,
|
||||
wantValue: 42.1,
|
||||
},
|
||||
{
|
||||
name: "Key.String() correctly returns keys's internal string value",
|
||||
value: k.String("foo").Value,
|
||||
wantType: attribute.STRING,
|
||||
wantValue: "foo",
|
||||
},
|
||||
{
|
||||
name: "Key.Int() correctly returns keys's internal signed integral value",
|
||||
value: k.Int(bli.intValue).Value,
|
||||
wantType: bli.signedType,
|
||||
wantValue: bli.signedValue,
|
||||
},
|
||||
{
|
||||
name: "Key.Array([]int64) correctly returns keys's internal int64 values",
|
||||
value: k.Array([]int64{42, 43}).Value,
|
||||
wantType: attribute.ARRAY,
|
||||
wantValue: [2]int64{42, 43},
|
||||
},
|
||||
{
|
||||
name: "Key.Array([]float64) correctly returns keys's internal float64 values",
|
||||
value: k.Array([]float64{42, 43}).Value,
|
||||
wantType: attribute.ARRAY,
|
||||
wantValue: [2]float64{42, 43},
|
||||
},
|
||||
{
|
||||
name: "Key.Array([]string) correctly return key's internal string values",
|
||||
value: k.Array([]string{"foo", "bar"}).Value,
|
||||
wantType: attribute.ARRAY,
|
||||
wantValue: [2]string{"foo", "bar"},
|
||||
},
|
||||
{
|
||||
name: "Key.Array([]int) correctly returns keys's internal signed integral values",
|
||||
value: k.Array([]int{42, 43}).Value,
|
||||
wantType: attribute.ARRAY,
|
||||
wantValue: [2]int{42, 43},
|
||||
},
|
||||
{
|
||||
name: "Key.Array([][]int) refuses to create multi-dimensional array",
|
||||
value: k.Array([][]int{{1, 2}, {3, 4}}).Value,
|
||||
wantType: attribute.INVALID,
|
||||
wantValue: nil,
|
||||
},
|
||||
} {
|
||||
t.Logf("Running test case %s", testcase.name)
|
||||
if testcase.value.Type() != testcase.wantType {
|
||||
t.Errorf("wrong value type, got %#v, expected %#v", testcase.value.Type(), testcase.wantType)
|
||||
}
|
||||
if testcase.wantType == attribute.INVALID {
|
||||
continue
|
||||
}
|
||||
got := testcase.value.AsInterface()
|
||||
if diff := cmp.Diff(testcase.wantValue, got); diff != "" {
|
||||
t.Errorf("+got, -want: %s", diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type bitlessInfo struct {
|
||||
intValue int
|
||||
uintValue uint
|
||||
signedType attribute.Type
|
||||
signedValue interface{}
|
||||
}
|
||||
|
||||
func getBitlessInfo(i int) bitlessInfo {
|
||||
return bitlessInfo{
|
||||
intValue: i,
|
||||
uintValue: uint(i),
|
||||
signedType: attribute.INT64,
|
||||
signedValue: int64(i),
|
||||
}
|
||||
}
|
||||
|
||||
func TestAsArrayValue(t *testing.T) {
|
||||
v := attribute.ArrayValue([]int{1, 2, 3}).AsArray()
|
||||
// Ensure the returned dynamic type is stable.
|
||||
if got, want := reflect.TypeOf(v).Kind(), reflect.Array; got != want {
|
||||
t.Errorf("AsArray() returned %T, want %T", got, want)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user