Skip to content

Commit

Permalink
Allow dropping items from correlations + docs + cleanups (#454)
Browse files Browse the repository at this point in the history
* Drop entry from correlation map

Entry used to contain stuff like TTL, but right now the notion of
entry was dropped from the spec.

* Compute exact size of the correlations map

The map will be immutable, so spend some more time to ensure that we
will not unnecessarily waste some memory on a basically read-only map.

* Allow dropping keys from correlations map

This is to follow the spec. Before this change it was possible in an
awkward way by using Foreach to gather and filter the key-value pairs,
and then calling NewMap with a MultiKV MapUpdate.

* Document the correlation package

* Add missing license blurbs in correlation package

* Add tests for deleting items in correlations

* Factor out getting map size and test it

This is an implementation detail that can't be tested in a black-box
manner.

* Fix copyright dates

* Simplify/disambiguate keySet function parameters/return values

* Fix typo in Apply docs

* Fix test names

* Explain the nonsense of dropping keys from new map
  • Loading branch information
krnowak authored Feb 3, 2020
1 parent 6051c81 commit 574463c
Show file tree
Hide file tree
Showing 4 changed files with 318 additions and 71 deletions.
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]
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

0 comments on commit 574463c

Please sign in to comment.