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

Allow dropping items from correlations + docs + cleanups #454

Merged
merged 12 commits into from
Feb 3, 2020
19 changes: 17 additions & 2 deletions api/correlation/context.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
// Copyright 2020, 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 correlation

import (
Expand All @@ -10,12 +24,13 @@ type correlationsType struct{}

var correlationsKey = &correlationsType{}

// WithMap enters a Map into a new Context.
// WithMap returns a context with the Map entered into it.
func WithMap(ctx context.Context, m Map) context.Context {
return context.WithValue(ctx, correlationsKey, m)
}

// WithMap enters a key:value set into a new Context.
// NewContext returns a context with the map from passed context
// updated with the passed key-value pairs.
func NewContext(ctx context.Context, keyvalues ...core.KeyValue) context.Context {
return WithMap(ctx, FromContext(ctx).Apply(MapUpdate{
MultiKV: keyvalues,
Expand Down
19 changes: 19 additions & 0 deletions api/correlation/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright 2020, 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.

// This package implements the correlation functionality as specified
// in the OpenTelemetry specification. Currently it provides a data
// structure for storing correlations (Map) and a way of putting Map
// object into the context and retrieving it from context.
package correlation // import "go.opentelemetry.io/otel/api/correlation"
120 changes: 101 additions & 19 deletions api/correlation/map.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2019, OpenTelemetry Authors
// Copyright 2020, OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -18,21 +18,32 @@ import (
"go.opentelemetry.io/otel/api/core"
)

// TODO Comments needed! This was formerly known as distributedcontext.Map

type entry struct {
value core.Value
}

type rawMap map[core.Key]entry
type rawMap map[core.Key]core.Value
type keySet map[core.Key]struct{}

// Map is an immutable storage for correlations.
type Map struct {
m rawMap
}

// MapUpdate contains information about correlation changes to be
// made.
type MapUpdate struct {
// DropSingleK contains a single key to be dropped from
// correlations. Use this to avoid an overhead of a slice
// allocation if there is only one key to drop.
DropSingleK core.Key
// DropMultiK contains all the keys to be dropped from
// correlations.
DropMultiK []core.Key

// SingleKV contains a single key-value pair to be added to
// correlations. Use this to avoid an overhead of a slice
// allocation if there is only one key-value pair to add.
SingleKV core.KeyValue
MultiKV []core.KeyValue
// MultiKV contains all the key-value pairs to be added to
// correlations.
MultiKV []core.KeyValue
}

func newMap(raw rawMap) Map {
Expand All @@ -41,54 +52,125 @@ func newMap(raw rawMap) Map {
}
}

// NewEmptyMap creates an empty correlations map.
func NewEmptyMap() Map {
return newMap(nil)
}

// NewMap creates a map with the contents of the update applied. In
// this function, having an update with DropSingleK or DropMultiK
// makes no sense - those fields are effectively ignored.
func NewMap(update MapUpdate) Map {
return NewEmptyMap().Apply(update)
}

// Apply creates a copy of the map with the contents of the update
// applied. Apply will first drop the keys from DropSingleK and
// DropMultiK, then add key-value pairs from SingleKV and MultiKV.
func (m Map) Apply(update MapUpdate) Map {
r := make(rawMap, len(m.m)+len(update.MultiKV))
delSet, addSet := getModificationSets(update)
mapSize := getNewMapSize(m.m, delSet, addSet)

r := make(rawMap, mapSize)
for k, v := range m.m {
// do not copy items we want to drop
if _, ok := delSet[k]; ok {
continue
}
// do not copy items we would overwrite
if _, ok := addSet[k]; ok {
continue
}
r[k] = v
}
if update.SingleKV.Key.Defined() {
r[update.SingleKV.Key] = entry{
value: update.SingleKV.Value,
}
r[update.SingleKV.Key] = update.SingleKV.Value
}
for _, kv := range update.MultiKV {
r[kv.Key] = entry{
value: kv.Value,
}
r[kv.Key] = kv.Value
}
if len(r) == 0 {
r = nil
}
return newMap(r)
}

func getModificationSets(update MapUpdate) (delSet, addSet keySet) {
deletionsCount := len(update.DropMultiK)
if update.DropSingleK.Defined() {
deletionsCount++
}
if deletionsCount > 0 {
delSet = make(map[core.Key]struct{}, deletionsCount)
for _, k := range update.DropMultiK {
delSet[k] = struct{}{}
}
if update.DropSingleK.Defined() {
delSet[update.DropSingleK] = struct{}{}
}
}

additionsCount := len(update.MultiKV)
if update.SingleKV.Key.Defined() {
additionsCount++
}
if additionsCount > 0 {
addSet = make(map[core.Key]struct{}, additionsCount)
for _, k := range update.MultiKV {
addSet[k.Key] = struct{}{}
}
if update.SingleKV.Key.Defined() {
addSet[update.SingleKV.Key] = struct{}{}
}
}

return
}

func getNewMapSize(m rawMap, delSet, addSet keySet) int {
mapSizeDiff := 0
for k := range addSet {
if _, ok := m[k]; !ok {
mapSizeDiff++
}
}
for k := range delSet {
if _, ok := m[k]; ok {
if _, inAddSet := addSet[k]; !inAddSet {
mapSizeDiff--
}
}
}
return len(m) + mapSizeDiff
}

// Value gets a value from correlations map and returns a boolean
// value indicating whether the key exist in the map.
func (m Map) Value(k core.Key) (core.Value, bool) {
entry, ok := m.m[k]
return entry.value, ok
value, ok := m.m[k]
krnowak marked this conversation as resolved.
Show resolved Hide resolved
return value, ok
}

// HasValue returns a boolean value indicating whether the key exist
// in the map.
func (m Map) HasValue(k core.Key) bool {
_, has := m.Value(k)
return has
}

// Len returns a length of the map.
func (m Map) Len() int {
return len(m.m)
}

// Foreach calls a passed callback once on each key-value pair until
// all the key-value pairs of the map were iterated or the callback
// returns false, whichever happens first.
func (m Map) Foreach(f func(kv core.KeyValue) bool) {
for k, v := range m.m {
if !f(core.KeyValue{
Key: k,
Value: v.value,
Value: v,
}) {
return
}
Expand Down
Loading