Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proof-of-concept: OTel Resource scope and namespace API #427

Closed
wants to merge 22 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
226 changes: 226 additions & 0 deletions api/context/internal/set.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
// Copyright 2019, 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 internal

import (
"reflect"
"sort"
"sync"
"sync/atomic"
"unsafe"

"go.opentelemetry.io/otel/api/core"
)

const maxConcurrentEncoders = 3

type sorted []core.KeyValue

// Set is the internal representation for LabelSet. It manages an
// immutable set of labels with an internal cache for storing encoded
// labels.
//
// Note this has a remarkably similar API to the
// `distributedcontext.Map` type. This code, which uses a sorted
// KeyValue list, could be used to implement `distributedcontext.Map`,
// which uses a map[Key]Value.
type Set struct {
ordered sorted

lock sync.Mutex
encoders [maxConcurrentEncoders]unsafe.Pointer
encoded [maxConcurrentEncoders]string
}

type update struct {
singleKV core.KeyValue
multiKV []core.KeyValue
}

var emptySet = &Set{}

func EmptySet() *Set {
return emptySet
}

// Ordered returns the labels in a specified order, according to the
// Batcher.
func (l *Set) Ordered() []core.KeyValue {
if l == nil {
return nil
}
return l.ordered
}

func (l *Set) Value(k core.Key) (core.Value, bool) {
if l == nil {
return core.Value{}, false
}
idx := sort.Search(len(l.ordered), func(i int) bool {
return l.ordered[i].Key >= k
})
if idx < len(l.ordered) && k == l.ordered[idx].Key {
return l.ordered[idx].Value, true
}
return core.Value{}, false
}

func (l *Set) HasValue(k core.Key) bool {
if l == nil {
return false
}
_, ok := l.Value(k)
return ok
}

// Encoded is a pre-encoded form of the ordered labels.
func (l *Set) Encoded(enc core.LabelEncoder) string {
if l == nil || enc == nil {
return ""
}

vptr := reflect.ValueOf(enc)
if vptr.Kind() != reflect.Ptr {
panic("core.LabelEncoder implementations must use pointer receivers")
}
myself := unsafe.Pointer(vptr.Pointer())

idx := 0
for idx := 0; idx < maxConcurrentEncoders; idx++ {
ptr := atomic.LoadPointer(&l.encoders[idx])

if ptr == myself {
// fmt.Println("Case A")
return l.encoded[idx]
}

if ptr == nil {
// fmt.Println("Case B", idx)
break
}
}

r := enc.Encode(l.ordered)

l.lock.Lock()
defer l.lock.Unlock()

for ; idx < maxConcurrentEncoders; idx++ {
ptr := atomic.LoadPointer(&l.encoders[idx])

if ptr != nil {
// fmt.Println("Case C")
continue
}

if ptr == nil {
// fmt.Println("Case D", idx)
atomic.StorePointer(&l.encoders[idx], myself)
l.encoded[idx] = r
break
}
}

// TODO add a slice for overflow, test for panics

return r
}

// Len returns the number of labels.
func (l *Set) Len() int {
if l == nil {
return 0
}
return len(l.ordered)
}

func (l *Set) Equals(o *Set) bool {
if l.Len() != o.Len() {
return false
}
for i := 0; i < l.Len(); i++ {
if l.ordered[i] != o.ordered[i] {
return false
}
}
return true
}

func (l *Set) AddOne(kv core.KeyValue) *Set {
return l.apply(update{singleKV: kv})
}

func (l *Set) AddMany(kvs ...core.KeyValue) *Set {
return l.apply(update{multiKV: kvs})
}

func (l *Set) apply(update update) *Set {
if l == nil {
l = emptySet
}
one := 0
if update.singleKV.Key.Defined() {
one = 1
}

set := make([]core.KeyValue, 0, l.Len()+len(update.multiKV)+one)
set = append(set, l.ordered...)
if one == 1 {
set = append(set, update.singleKV)
}

set = append(set, update.multiKV...)

return NewSet(set...)
}

// NewSet builds a Labels object, consisting of an ordered set of
// labels, de-duplicated with last-value-wins semantics.
func NewSet(kvs ...core.KeyValue) *Set {
// Check for empty set.
if len(kvs) == 0 {
return emptySet
}

ls := &Set{
ordered: kvs,
}

// Sort and de-duplicate.
sort.Stable(&ls.ordered)
oi := 1
for i := 1; i < len(ls.ordered); i++ {
if ls.ordered[i-1].Key == ls.ordered[i].Key {
ls.ordered[oi-1] = ls.ordered[i]
continue
}
ls.ordered[oi] = ls.ordered[i]
oi++
}
ls.ordered = ls.ordered[0:oi]
return ls
}

func (l *sorted) Len() int {
return len(*l)
}

func (l *sorted) Swap(i, j int) {
(*l)[i], (*l)[j] = (*l)[j], (*l)[i]
}

func (l *sorted) Less(i, j int) bool {
return (*l)[i].Key < (*l)[j].Key
}
15 changes: 8 additions & 7 deletions sdk/metric/labelencoder.go → api/context/label/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package metric
package label

import (
"bytes"
"sync"

"go.opentelemetry.io/otel/api/core"
export "go.opentelemetry.io/otel/sdk/export/metric"
)

type defaultLabelEncoder struct {
type defaultEncoder 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. This pool reduces the number of
Expand All @@ -33,10 +32,12 @@ type defaultLabelEncoder struct {
pool sync.Pool // *bytes.Buffer
}

var _ export.LabelEncoder = &defaultLabelEncoder{}
type Encoder = core.LabelEncoder

func NewDefaultLabelEncoder() export.LabelEncoder {
return &defaultLabelEncoder{
var _ Encoder = &defaultEncoder{}

func NewDefaultEncoder() Encoder {
return &defaultEncoder{
pool: sync.Pool{
New: func() interface{} {
return &bytes.Buffer{}
Expand All @@ -45,7 +46,7 @@ func NewDefaultLabelEncoder() export.LabelEncoder {
}
}

func (d *defaultLabelEncoder) Encode(labels []core.KeyValue) string {
func (d *defaultEncoder) Encode(labels []core.KeyValue) string {
buf := d.pool.Get().(*bytes.Buffer)
defer d.pool.Put(buf)
buf.Reset()
Expand Down
97 changes: 97 additions & 0 deletions api/context/label/set.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright 2019, 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 label

import (
"go.opentelemetry.io/otel/api/context/internal"
"go.opentelemetry.io/otel/api/core"
)

// Set represents an immutable set of labels, used to represent the
// "resources" of a Scope. LabelSets contain a unique mapping from
// Key to Value; duplicates are treated by retaining the last value.
//
// Scopes contain these resources, so that end users will rarely need
// to handle these directly.
//
// Set supports caching the encoded representation of the set of
// labels based on a user-supplied LabelEncoder.
type Set struct {
set *internal.Set
}

// Empty returns a set with zero keys.
func Empty() Set {
return Set{internal.EmptySet()}
}

// NewSet constructs a set from a list of KeyValues. Ordinarily users
// will not construct these directly, as the Scope represents the
// current resources as a label set.
func NewSet(kvs ...core.KeyValue) Set {
return Set{internal.NewSet(kvs...)}
}

// AddOne adds a single KeyValue to the set.
func (s Set) AddOne(kv core.KeyValue) Set {
return Set{s.set.AddOne(kv)}

}

// AddMany adds multiple KeyValues to the set.
func (s Set) AddMany(kvs ...core.KeyValue) Set {
return Set{s.set.AddMany(kvs...)}
}

// Value returns the value associated with the supplied Key and a
// boolean to indicate whether it was found.
func (s Set) Value(k core.Key) (core.Value, bool) {
return s.set.Value(k)
}

// HasValue returns true if the set contains a value associated with a Key.
func (s Set) HasValue(k core.Key) bool {
return s.set.HasValue(k)
}

// Len returns the number of labels in the set.
func (s Set) Len() int {
return s.set.Len()
}

// Ordered returns the label set sorted alphanumerically by Key name.
func (s Set) Ordered() []core.KeyValue {
return s.set.Ordered()
}

// Foreach calls the provided callback for each label in the set.
func (s Set) Foreach(f func(kv core.KeyValue) bool) {
for _, kv := range s.set.Ordered() {
if !f(kv) {
return
}
}
}

// Equals tests whether two sets of labels are identical.
func (s Set) Equals(t Set) bool {
return s.set.Equals(t.set)
}

// Encoded returns the computed encoding for a label set. Encoded
// values are cached with the set, to avoid recomputing them.
func (s Set) Encoded(enc core.LabelEncoder) string {
return s.set.Encoded(enc)
}
Loading