mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2024-12-12 10:04:29 +02:00
Allow dropping items from correlations + docs + cleanups (#454)
* 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
This commit is contained in:
parent
6051c81493
commit
574463c9ef
@ -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 (
|
||||
@ -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,
|
||||
|
19
api/correlation/doc.go
Normal file
19
api/correlation/doc.go
Normal file
@ -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"
|
@ -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.
|
||||
@ -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 {
|
||||
@ -41,28 +52,42 @@ 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
|
||||
@ -70,25 +95,82 @@ func (m Map) Apply(update MapUpdate) Map {
|
||||
return newMap(r)
|
||||
}
|
||||
|
||||
func (m Map) Value(k core.Key) (core.Value, bool) {
|
||||
entry, ok := m.m[k]
|
||||
return entry.value, ok
|
||||
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) {
|
||||
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
|
||||
}
|
||||
|
@ -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.
|
||||
@ -22,97 +22,15 @@ import (
|
||||
"go.opentelemetry.io/otel/api/key"
|
||||
)
|
||||
|
||||
type testCase struct {
|
||||
name string
|
||||
value MapUpdate
|
||||
init []int
|
||||
wantKVs []core.KeyValue
|
||||
}
|
||||
|
||||
func TestMap(t *testing.T) {
|
||||
for _, testcase := range []struct {
|
||||
name string
|
||||
value MapUpdate
|
||||
init []int
|
||||
wantKVs []core.KeyValue
|
||||
}{
|
||||
{
|
||||
name: "NewMap with MultiKV",
|
||||
value: MapUpdate{MultiKV: []core.KeyValue{
|
||||
key.Int64("key1", 1),
|
||||
key.String("key2", "val2")},
|
||||
},
|
||||
init: []int{},
|
||||
wantKVs: []core.KeyValue{
|
||||
key.Int64("key1", 1),
|
||||
key.String("key2", "val2"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "NewMap with SingleKV",
|
||||
value: MapUpdate{SingleKV: key.String("key1", "val1")},
|
||||
init: []int{},
|
||||
wantKVs: []core.KeyValue{
|
||||
key.String("key1", "val1"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "NewMap with MapUpdate",
|
||||
value: MapUpdate{SingleKV: key.Int64("key1", 3),
|
||||
MultiKV: []core.KeyValue{
|
||||
key.String("key1", ""),
|
||||
key.String("key2", "val2")},
|
||||
},
|
||||
init: []int{},
|
||||
wantKVs: []core.KeyValue{
|
||||
key.String("key1", ""),
|
||||
key.String("key2", "val2"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "NewMap with empty MapUpdate",
|
||||
value: MapUpdate{MultiKV: []core.KeyValue{}},
|
||||
init: []int{},
|
||||
wantKVs: []core.KeyValue{},
|
||||
},
|
||||
{
|
||||
name: "Map with MultiKV",
|
||||
value: MapUpdate{MultiKV: []core.KeyValue{
|
||||
key.Int64("key1", 1),
|
||||
key.String("key2", "val2")},
|
||||
},
|
||||
init: []int{5},
|
||||
wantKVs: []core.KeyValue{
|
||||
key.Int64("key1", 1),
|
||||
key.String("key2", "val2"),
|
||||
key.Int("key5", 5),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Map with SingleKV",
|
||||
value: MapUpdate{SingleKV: key.String("key1", "val1")},
|
||||
init: []int{5},
|
||||
wantKVs: []core.KeyValue{
|
||||
key.String("key1", "val1"),
|
||||
key.Int("key5", 5),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Map with MapUpdate",
|
||||
value: MapUpdate{SingleKV: key.Int64("key1", 3),
|
||||
MultiKV: []core.KeyValue{
|
||||
key.String("key1", ""),
|
||||
key.String("key2", "val2")},
|
||||
},
|
||||
init: []int{5},
|
||||
wantKVs: []core.KeyValue{
|
||||
key.String("key1", ""),
|
||||
key.String("key2", "val2"),
|
||||
key.Int("key5", 5),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Map with empty MapUpdate",
|
||||
value: MapUpdate{MultiKV: []core.KeyValue{}},
|
||||
init: []int{5},
|
||||
wantKVs: []core.KeyValue{
|
||||
key.Int("key5", 5),
|
||||
},
|
||||
},
|
||||
} {
|
||||
for _, testcase := range getTestCases() {
|
||||
t.Logf("Running test case %s", testcase.name)
|
||||
var got Map
|
||||
if len(testcase.init) > 0 {
|
||||
@ -144,12 +62,225 @@ func TestMap(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSizeComputation(t *testing.T) {
|
||||
for _, testcase := range getTestCases() {
|
||||
t.Logf("Running test case %s", testcase.name)
|
||||
var initMap Map
|
||||
if len(testcase.init) > 0 {
|
||||
initMap = makeTestMap(testcase.init)
|
||||
} else {
|
||||
initMap = NewEmptyMap()
|
||||
}
|
||||
gotMap := initMap.Apply(testcase.value)
|
||||
|
||||
delSet, addSet := getModificationSets(testcase.value)
|
||||
mapSize := getNewMapSize(initMap.m, delSet, addSet)
|
||||
|
||||
if gotMap.Len() != mapSize {
|
||||
t.Errorf("Expected computed size to be %d, got %d", gotMap.Len(), mapSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getTestCases() []testCase {
|
||||
return []testCase{
|
||||
{
|
||||
name: "New map with MultiKV",
|
||||
value: MapUpdate{MultiKV: []core.KeyValue{
|
||||
key.Int64("key1", 1),
|
||||
key.String("key2", "val2")},
|
||||
},
|
||||
init: []int{},
|
||||
wantKVs: []core.KeyValue{
|
||||
key.Int64("key1", 1),
|
||||
key.String("key2", "val2"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "New map with SingleKV",
|
||||
value: MapUpdate{SingleKV: key.String("key1", "val1")},
|
||||
init: []int{},
|
||||
wantKVs: []core.KeyValue{
|
||||
key.String("key1", "val1"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "New map with both add fields",
|
||||
value: MapUpdate{SingleKV: key.Int64("key1", 3),
|
||||
MultiKV: []core.KeyValue{
|
||||
key.String("key1", ""),
|
||||
key.String("key2", "val2")},
|
||||
},
|
||||
init: []int{},
|
||||
wantKVs: []core.KeyValue{
|
||||
key.String("key1", ""),
|
||||
key.String("key2", "val2"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "New map with empty MapUpdate",
|
||||
value: MapUpdate{},
|
||||
init: []int{},
|
||||
wantKVs: []core.KeyValue{},
|
||||
},
|
||||
{
|
||||
name: "New map with DropSingleK",
|
||||
value: MapUpdate{DropSingleK: core.Key("key1")},
|
||||
init: []int{},
|
||||
wantKVs: []core.KeyValue{},
|
||||
},
|
||||
{
|
||||
name: "New map with DropMultiK",
|
||||
value: MapUpdate{DropMultiK: []core.Key{
|
||||
core.Key("key1"), core.Key("key2"),
|
||||
}},
|
||||
init: []int{},
|
||||
wantKVs: []core.KeyValue{},
|
||||
},
|
||||
{
|
||||
name: "New map with both drop fields",
|
||||
value: MapUpdate{
|
||||
DropSingleK: core.Key("key1"),
|
||||
DropMultiK: []core.Key{
|
||||
core.Key("key1"),
|
||||
core.Key("key2"),
|
||||
},
|
||||
},
|
||||
init: []int{},
|
||||
wantKVs: []core.KeyValue{},
|
||||
},
|
||||
{
|
||||
name: "New map with all fields",
|
||||
value: MapUpdate{
|
||||
DropSingleK: core.Key("key1"),
|
||||
DropMultiK: []core.Key{
|
||||
core.Key("key1"),
|
||||
core.Key("key2"),
|
||||
},
|
||||
SingleKV: key.String("key4", "val4"),
|
||||
MultiKV: []core.KeyValue{
|
||||
key.String("key1", ""),
|
||||
key.String("key2", "val2"),
|
||||
key.String("key3", "val3"),
|
||||
},
|
||||
},
|
||||
init: []int{},
|
||||
wantKVs: []core.KeyValue{
|
||||
key.String("key1", ""),
|
||||
key.String("key2", "val2"),
|
||||
key.String("key3", "val3"),
|
||||
key.String("key4", "val4"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Existing map with MultiKV",
|
||||
value: MapUpdate{MultiKV: []core.KeyValue{
|
||||
key.Int64("key1", 1),
|
||||
key.String("key2", "val2")},
|
||||
},
|
||||
init: []int{5},
|
||||
wantKVs: []core.KeyValue{
|
||||
key.Int64("key1", 1),
|
||||
key.String("key2", "val2"),
|
||||
key.Int("key5", 5),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Existing map with SingleKV",
|
||||
value: MapUpdate{SingleKV: key.String("key1", "val1")},
|
||||
init: []int{5},
|
||||
wantKVs: []core.KeyValue{
|
||||
key.String("key1", "val1"),
|
||||
key.Int("key5", 5),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Existing map with both add fields",
|
||||
value: MapUpdate{SingleKV: key.Int64("key1", 3),
|
||||
MultiKV: []core.KeyValue{
|
||||
key.String("key1", ""),
|
||||
key.String("key2", "val2")},
|
||||
},
|
||||
init: []int{5},
|
||||
wantKVs: []core.KeyValue{
|
||||
key.String("key1", ""),
|
||||
key.String("key2", "val2"),
|
||||
key.Int("key5", 5),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Existing map with empty MapUpdate",
|
||||
value: MapUpdate{},
|
||||
init: []int{5},
|
||||
wantKVs: []core.KeyValue{
|
||||
key.Int("key5", 5),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Existing map with DropSingleK",
|
||||
value: MapUpdate{DropSingleK: core.Key("key1")},
|
||||
init: []int{1, 5},
|
||||
wantKVs: []core.KeyValue{
|
||||
key.Int("key5", 5),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Existing map with DropMultiK",
|
||||
value: MapUpdate{DropMultiK: []core.Key{
|
||||
core.Key("key1"), core.Key("key2"),
|
||||
}},
|
||||
init: []int{1, 5},
|
||||
wantKVs: []core.KeyValue{
|
||||
key.Int("key5", 5),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Existing map with both drop fields",
|
||||
value: MapUpdate{
|
||||
DropSingleK: core.Key("key1"),
|
||||
DropMultiK: []core.Key{
|
||||
core.Key("key1"),
|
||||
core.Key("key2"),
|
||||
},
|
||||
},
|
||||
init: []int{1, 2, 5},
|
||||
wantKVs: []core.KeyValue{
|
||||
key.Int("key5", 5),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Existing map with all the fields",
|
||||
value: MapUpdate{
|
||||
DropSingleK: core.Key("key1"),
|
||||
DropMultiK: []core.Key{
|
||||
core.Key("key1"),
|
||||
core.Key("key2"),
|
||||
core.Key("key5"),
|
||||
core.Key("key6"),
|
||||
},
|
||||
SingleKV: key.String("key4", "val4"),
|
||||
MultiKV: []core.KeyValue{
|
||||
key.String("key1", ""),
|
||||
key.String("key2", "val2"),
|
||||
key.String("key3", "val3"),
|
||||
},
|
||||
},
|
||||
init: []int{5, 6, 7},
|
||||
wantKVs: []core.KeyValue{
|
||||
key.String("key1", ""),
|
||||
key.String("key2", "val2"),
|
||||
key.String("key3", "val3"),
|
||||
key.String("key4", "val4"),
|
||||
key.Int("key7", 7),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func makeTestMap(ints []int) Map {
|
||||
r := make(rawMap, len(ints))
|
||||
for _, v := range ints {
|
||||
r[core.Key(fmt.Sprintf("key%d", v))] = entry{
|
||||
value: core.Int(v),
|
||||
}
|
||||
r[core.Key(fmt.Sprintf("key%d", v))] = core.Int(v)
|
||||
}
|
||||
return newMap(r)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user