| // Copyright The OpenTelemetry Authors | |
| // SPDX-License-Identifier: Apache-2.0 | |
| package attribute | |
| import ( | |
| "cmp" | |
| "encoding/json" | |
| "reflect" | |
| "slices" | |
| "sort" | |
| "go.opentelemetry.io/otel/attribute/internal/xxhash" | |
| ) | |
| type ( | |
| // Set is the representation for a distinct attribute set. It manages an | |
| // immutable set of attributes, with an internal cache for storing | |
| // attribute encodings. | |
| // | |
| // This type will remain comparable for backwards compatibility. The | |
| // equivalence of Sets across versions is not guaranteed to be stable. | |
| // Prior versions may find two Sets to be equal or not when compared | |
| // directly (i.e. ==), but subsequent versions may not. Users should use | |
| // the Equals method to ensure stable equivalence checking. | |
| // | |
| // Users should also use the Distinct returned from Equivalent as a map key | |
| // instead of a Set directly. Set has relatively poor performance when used | |
| // as a map key compared to Distinct. | |
| Set struct { | |
| hash uint64 | |
| data any | |
| } | |
| // Distinct is an identifier of a Set which is very likely to be unique. | |
| // | |
| // Distinct should be used as a map key instead of a Set for to provide better | |
| // performance for map operations. | |
| Distinct struct { | |
| hash uint64 | |
| } | |
| // Sortable implements sort.Interface, used for sorting KeyValue. | |
| // | |
| // Deprecated: This type is no longer used. It was added as a performance | |
| // optimization for Go < 1.21 that is no longer needed (Go < 1.21 is no | |
| // longer supported by the module). | |
| Sortable []KeyValue | |
| ) | |
| // Compile time check these types remain comparable. | |
| var ( | |
| _ = isComparable(Set{}) | |
| _ = isComparable(Distinct{}) | |
| ) | |
| func isComparable[T comparable](t T) T { return t } | |
| var ( | |
| // keyValueType is used in computeDistinctReflect. | |
| keyValueType = reflect.TypeFor[KeyValue]() | |
| // emptyHash is the hash of an empty set. | |
| emptyHash = xxhash.New().Sum64() | |
| // userDefinedEmptySet is an empty set. It was mistakenly exposed to users | |
| // as something they can assign to, so it must remain addressable and | |
| // mutable. | |
| // | |
| // This is kept for backwards compatibility, but should not be used in new code. | |
| userDefinedEmptySet = &Set{ | |
| hash: emptyHash, | |
| data: [0]KeyValue{}, | |
| } | |
| emptySet = Set{ | |
| hash: emptyHash, | |
| data: [0]KeyValue{}, | |
| } | |
| ) | |
| // EmptySet returns a reference to a Set with no elements. | |
| // | |
| // This is a convenience provided for optimized calling utility. | |
| func EmptySet() *Set { | |
| // Continue to return the pointer to the user-defined empty set for | |
| // backwards-compatibility. | |
| // | |
| // New code should not use this, instead use emptySet. | |
| return userDefinedEmptySet | |
| } | |
| // Valid reports whether this value refers to a valid Set. | |
| func (d Distinct) Valid() bool { return d.hash != 0 } | |
| // reflectValue abbreviates reflect.ValueOf(d). | |
| func (l Set) reflectValue() reflect.Value { | |
| return reflect.ValueOf(l.data) | |
| } | |
| // Len returns the number of attributes in this set. | |
| func (l *Set) Len() int { | |
| if l == nil || l.hash == 0 { | |
| return 0 | |
| } | |
| return l.reflectValue().Len() | |
| } | |
| // Get returns the KeyValue at ordered position idx in this set. | |
| func (l *Set) Get(idx int) (KeyValue, bool) { | |
| if l == nil || l.hash == 0 { | |
| return KeyValue{}, false | |
| } | |
| value := l.reflectValue() | |
| 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 || l.hash == 0 { | |
| return Value{}, false | |
| } | |
| rValue := l.reflectValue() | |
| 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 reports 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 attributes in this set. | |
| func (l *Set) Iter() Iterator { | |
| return Iterator{ | |
| storage: l, | |
| idx: -1, | |
| } | |
| } | |
| // ToSlice returns the set of attributes 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. Equal Distinct | |
| // values are very likely to be equivalent attribute Sets. Distinct value of any | |
| // attribute 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.hash == 0 { | |
| return Distinct{hash: emptySet.hash} | |
| } | |
| return Distinct{hash: l.hash} | |
| } | |
| // Equals reports whether the argument set is equivalent to this set. | |
| func (l *Set) Equals(o *Set) bool { | |
| if l.Equivalent() != o.Equivalent() { | |
| return false | |
| } | |
| if l == nil || l.hash == 0 { | |
| l = &emptySet | |
| } | |
| if o == nil || o.hash == 0 { | |
| o = &emptySet | |
| } | |
| return l.data == o.data | |
| } | |
| // Encoded returns the encoded form of this set, according to encoder. | |
| func (l *Set) Encoded(encoder Encoder) string { | |
| if l == nil || encoder == nil { | |
| return "" | |
| } | |
| return encoder.Encode(l.Iter()) | |
| } | |
| // 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 { | |
| s, _ := NewSetWithFiltered(kvs, nil) | |
| return s | |
| } | |
| // NewSetWithSortable returns a new Set. See the documentation for | |
| // NewSetWithSortableFiltered for more details. | |
| // | |
| // This call includes a Sortable option as a memory optimization. | |
| // | |
| // Deprecated: Use [NewSet] instead. | |
| func NewSetWithSortable(kvs []KeyValue, _ *Sortable) Set { | |
| s, _ := NewSetWithFiltered(kvs, nil) | |
| return s | |
| } | |
| // NewSetWithFiltered returns a new Set. See the documentation for | |
| // NewSetWithSortableFiltered for more details. | |
| // | |
| // This call includes a Filter to include/exclude attribute keys from the | |
| // return value. Excluded keys are returned as a slice of attribute values. | |
| func NewSetWithFiltered(kvs []KeyValue, filter Filter) (Set, []KeyValue) { | |
| // Check for empty set. | |
| if len(kvs) == 0 { | |
| return emptySet, nil | |
| } | |
| // Stable sort so the following de-duplication can implement | |
| // last-value-wins semantics. | |
| slices.SortStableFunc(kvs, func(a, b KeyValue) int { | |
| return cmp.Compare(a.Key, b.Key) | |
| }) | |
| 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] | |
| } | |
| kvs = kvs[position:] | |
| if filter != nil { | |
| if div := filteredToFront(kvs, filter); div != 0 { | |
| return newSet(kvs[div:]), kvs[:div] | |
| } | |
| } | |
| return newSet(kvs), nil | |
| } | |
| // 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 attributes, by attribute.EncoderID. | |
| // This value should not be copied after its first use. | |
| // | |
| // The second []KeyValue return value is a list of attributes that were | |
| // excluded by the Filter (if non-nil). | |
| // | |
| // Deprecated: Use [NewSetWithFiltered] instead. | |
| func NewSetWithSortableFiltered(kvs []KeyValue, _ *Sortable, filter Filter) (Set, []KeyValue) { | |
| return NewSetWithFiltered(kvs, filter) | |
| } | |
| // filteredToFront filters slice in-place using keep function. All KeyValues that need to | |
| // be removed are moved to the front. All KeyValues that need to be kept are | |
| // moved (in-order) to the back. The index for the first KeyValue to be kept is | |
| // returned. | |
| func filteredToFront(slice []KeyValue, keep Filter) int { | |
| n := len(slice) | |
| j := n | |
| for i := n - 1; i >= 0; i-- { | |
| if keep(slice[i]) { | |
| j-- | |
| slice[i], slice[j] = slice[j], slice[i] | |
| } | |
| } | |
| return j | |
| } | |
| // 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 l == nil { | |
| return emptySet, nil | |
| } | |
| if re == nil { | |
| return *l, nil | |
| } | |
| // Iterate in reverse to the first attribute that will be filtered out. | |
| n := l.Len() | |
| first := n - 1 | |
| for ; first >= 0; first-- { | |
| kv, _ := l.Get(first) | |
| if !re(kv) { | |
| break | |
| } | |
| } | |
| // No attributes will be dropped, return the immutable Set l and nil. | |
| if first < 0 { | |
| return *l, nil | |
| } | |
| // Copy now that we know we need to return a modified set. | |
| // | |
| // Do not do this in-place on the underlying storage of *Set l. Sets are | |
| // immutable and filtering should not change this. | |
| slice := l.ToSlice() | |
| // Don't re-iterate the slice if only slice[0] is filtered. | |
| if first == 0 { | |
| // It is safe to assume len(slice) >= 1 given we found at least one | |
| // attribute above that needs to be filtered out. | |
| return newSet(slice[1:]), slice[:1] | |
| } | |
| // Move the filtered slice[first] to the front (preserving order). | |
| kv := slice[first] | |
| copy(slice[1:first+1], slice[:first]) | |
| slice[0] = kv | |
| // Do not re-evaluate re(slice[first+1:]). | |
| div := filteredToFront(slice[1:first+1], re) + 1 | |
| return newSet(slice[div:]), slice[:div] | |
| } | |
| // newSet returns a new set based on the sorted and uniqued kvs. | |
| func newSet(kvs []KeyValue) Set { | |
| s := Set{ | |
| hash: hashKVs(kvs), | |
| data: computeDataFixed(kvs), | |
| } | |
| if s.data == nil { | |
| s.data = computeDataReflect(kvs) | |
| } | |
| return s | |
| } | |
| // computeDataFixed computes a Set data for small slices. It returns nil if the | |
| // input is too large for this code path. | |
| func computeDataFixed(kvs []KeyValue) any { | |
| switch len(kvs) { | |
| case 1: | |
| return [1]KeyValue(kvs) | |
| case 2: | |
| return [2]KeyValue(kvs) | |
| case 3: | |
| return [3]KeyValue(kvs) | |
| case 4: | |
| return [4]KeyValue(kvs) | |
| case 5: | |
| return [5]KeyValue(kvs) | |
| case 6: | |
| return [6]KeyValue(kvs) | |
| case 7: | |
| return [7]KeyValue(kvs) | |
| case 8: | |
| return [8]KeyValue(kvs) | |
| case 9: | |
| return [9]KeyValue(kvs) | |
| case 10: | |
| return [10]KeyValue(kvs) | |
| default: | |
| return nil | |
| } | |
| } | |
| // computeDataReflect computes a Set data using reflection, works for any size | |
| // input. | |
| func computeDataReflect(kvs []KeyValue) any { | |
| at := reflect.New(reflect.ArrayOf(len(kvs), keyValueType)).Elem() | |
| for i, keyValue := range kvs { | |
| *at.Index(i).Addr().Interface().(*KeyValue) = keyValue | |
| } | |
| return at.Interface() | |
| } | |
| // String returns a string representation of the Set using the | |
| // [OpenTelemetry Attribute Collection representation for non-OTLP protocols] | |
| // rules. | |
| // | |
| // The Set is encoded as a JSON object. A nil or empty Set is encoded as an | |
| // empty JSON object. | |
| // | |
| // [OpenTelemetry Attribute Collection representation for non-OTLP protocols]: https://opentelemetry.io/docs/specs/otel/common/#attribute-collection-representation-for-non-otlp | |
| func (l *Set) String() string { | |
| if l == nil || l.hash == 0 { | |
| return "{}" | |
| } | |
| return formatMapValue(l.data) | |
| } | |
| // MarshalJSON returns the JSON encoding of the Set. | |
| func (l *Set) MarshalJSON() ([]byte, error) { | |
| return json.Marshal(l.data) | |
| } | |
| // MarshalLog is the marshaling function used by the logging system to represent this Set. | |
| func (l Set) MarshalLog() any { | |
| kvs := make(map[string]string) | |
| for _, kv := range l.ToSlice() { | |
| kvs[string(kv.Key)] = kv.Value.String() | |
| } | |
| return kvs | |
| } | |
| // 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 | |
| } |