diff --git a/Makefile b/Makefile index 06ea0d376a7..e1ef0d7fc97 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,8 @@ ALL_DOCS := $(shell find . -name '*.md' -type f | sort) ALL_GO_MOD_DIRS := $(shell find . -type f -name 'go.mod' -exec dirname {} \; | sort) GOTEST=go test -GOTEST_OPT?=-v -race -timeout 30s +GOTEST_OPT?=-v -timeout 30s +GOTEST_OPT_WITH_RACE = $(GOTEST_OPT) -race GOTEST_OPT_WITH_COVERAGE = $(GOTEST_OPT) -coverprofile=coverage.txt -covermode=atomic .DEFAULT_GOAL := precommit @@ -62,6 +63,7 @@ test-clean-work-tree: .PHONY: test test: examples $(GOTEST) $(GOTEST_OPT) $(ALL_PKGS) + $(GOTEST) $(GOTEST_OPT_WITH_RACE) $(ALL_PKGS) .PHONY: test-386 test-386: diff --git a/api/core/number.go b/api/core/number.go new file mode 100644 index 00000000000..926ac4468b6 --- /dev/null +++ b/api/core/number.go @@ -0,0 +1,546 @@ +// 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 core + +//go:generate stringer -type=NumberKind + +import ( + "fmt" + "math" + "sync/atomic" + "unsafe" +) + +// NumberKind describes the data type of the Number. +type NumberKind int8 + +const ( + // Int64NumberKind means that the Number stores int64. + Int64NumberKind NumberKind = iota + // Float64NumberKind means that the Number stores float64. + Float64NumberKind + // Uint64NumberKind means that the Number stores uint64. + Uint64NumberKind +) + +// Number represents either an integral or a floating point value. It +// needs to be accompanied with a source of NumberKind that describes +// the actual type of the value stored within Number. +type Number uint64 + +// - constructors + +// NewZeroNumber +func NewZeroNumber(kind NumberKind) Number { + switch kind { + case Int64NumberKind: + return NewInt64Number(0) + case Float64NumberKind: + return NewFloat64Number(0.) + case Uint64NumberKind: + return NewUint64Number(0) + } + return Number(0) +} + +// NewNumberFromRaw creates a new Number from a raw value. +func NewNumberFromRaw(r uint64) Number { + return Number(r) +} + +// NewInt64Number creates an integral Number. +func NewInt64Number(i int64) Number { + return NewNumberFromRaw(int64ToRaw(i)) +} + +// NewFloat64Number creates a floating point Number. +func NewFloat64Number(f float64) Number { + return NewNumberFromRaw(float64ToRaw(f)) +} + +// NewInt64Number creates an integral Number. +func NewUint64Number(u uint64) Number { + return NewNumberFromRaw(uint64ToRaw(u)) +} + +// - as x + +// AsNumber gets the raw, uninterpreted raw value. Might be useful for +// some atomic operations. +func (n Number) AsNumber() Number { + return n +} + +// AsRaw gets the raw, uninterpreted raw value. Might be useful for +// some atomic operations. +func (n Number) AsRaw() uint64 { + return uint64(n) +} + +// AsInt64 assumes that the value contains an int64 and returns it as +// such. +func (n Number) AsInt64() int64 { + return rawToInt64(n.AsRaw()) +} + +// AsFloat64 assumes that the measurement value contains a float64 and +// returns it as such. +func (n Number) AsFloat64() float64 { + return rawToFloat64(n.AsRaw()) +} + +// AsUint64 assumes that the value contains an uint64 and returns it +// as such. +func (n Number) AsUint64() uint64 { + return rawToUint64(n.AsRaw()) +} + +// - as x atomic + +// AsNumberAtomic gets the raw, uninterpreted raw value. Might be useful for +// some atomic operations. +func (n *Number) AsNumberAtomic() Number { + return NewNumberFromRaw(n.AsRawAtomic()) +} + +// AsRawAtomic gets atomically the raw, uninterpreted raw value. Might +// be useful for some atomic operations. +func (n *Number) AsRawAtomic() uint64 { + return atomic.LoadUint64(n.AsRawPtr()) +} + +// AsInt64Atomic assumes that the number contains an int64 and +// atomically returns it as such. +func (n *Number) AsInt64Atomic() int64 { + return atomic.LoadInt64(n.AsInt64Ptr()) +} + +// AsFloat64 assumes that the measurement value contains a float64 and +// returns it as such. +func (n *Number) AsFloat64Atomic() float64 { + return rawToFloat64(n.AsRawAtomic()) +} + +// AsUint64Atomic assumes that the number contains an uint64 and +// atomically returns it as such. +func (n *Number) AsUint64Atomic() uint64 { + return atomic.LoadUint64(n.AsUint64Ptr()) +} + +// - as x ptr + +// AsRawPtr gets the pointer to the raw, uninterpreted raw +// value. Might be useful for some atomic operations. +func (n *Number) AsRawPtr() *uint64 { + return (*uint64)(n) +} + +func (n *Number) AsInt64Ptr() *int64 { + return rawPtrToInt64Ptr(n.AsRawPtr()) +} + +func (n *Number) AsFloat64Ptr() *float64 { + return rawPtrToFloat64Ptr(n.AsRawPtr()) +} + +func (n *Number) AsUint64Ptr() *uint64 { + return rawPtrToUint64Ptr(n.AsRawPtr()) +} + +// - coerce + +func (n Number) CoerceToInt64(kind NumberKind) int64 { + switch kind { + case Int64NumberKind: + return n.AsInt64() + case Float64NumberKind: + return int64(n.AsFloat64()) + case Uint64NumberKind: + return int64(n.AsUint64()) + default: + // you get what you deserve + return 0 + } +} + +func (n Number) CoerceToFloat64(kind NumberKind) float64 { + switch kind { + case Int64NumberKind: + return float64(n.AsInt64()) + case Float64NumberKind: + return n.AsFloat64() + case Uint64NumberKind: + return float64(n.AsUint64()) + default: + // you get what you deserve + return 0 + } +} + +func (n Number) CoerceToUint64(kind NumberKind) uint64 { + switch kind { + case Int64NumberKind: + return uint64(n.AsInt64()) + case Float64NumberKind: + return uint64(n.AsFloat64()) + case Uint64NumberKind: + return n.AsUint64() + default: + // you get what you deserve + return 0 + } +} + +// - set + +func (n *Number) SetNumber(nn Number) { + *n.AsRawPtr() = nn.AsRaw() +} + +func (n *Number) SetRaw(r uint64) { + *n.AsRawPtr() = r +} + +func (n *Number) SetInt64(i int64) { + *n.AsInt64Ptr() = i +} + +func (n *Number) SetFloat64(f float64) { + *n.AsFloat64Ptr() = f +} + +func (n *Number) SetUint64(u uint64) { + *n.AsUint64Ptr() = u +} + +// - set atomic + +func (n *Number) SetNumberAtomic(nn Number) { + atomic.StoreUint64(n.AsRawPtr(), nn.AsRaw()) +} + +func (n *Number) SetRawAtomic(r uint64) { + atomic.StoreUint64(n.AsRawPtr(), r) +} + +func (n *Number) SetInt64Atomic(i int64) { + atomic.StoreInt64(n.AsInt64Ptr(), i) +} + +func (n *Number) SetFloat64Atomic(f float64) { + atomic.StoreUint64(n.AsRawPtr(), float64ToRaw(f)) +} + +func (n *Number) SetUint64Atomic(u uint64) { + atomic.StoreUint64(n.AsUint64Ptr(), u) +} + +// - swap + +func (n *Number) SwapNumber(nn Number) Number { + old := *n + n.SetNumber(nn) + return old +} + +func (n *Number) SwapRaw(r uint64) uint64 { + old := n.AsRaw() + n.SetRaw(r) + return old +} + +func (n *Number) SwapInt64(i int64) int64 { + old := n.AsInt64() + n.SetInt64(i) + return old +} + +func (n *Number) SwapFloat64(f float64) float64 { + old := n.AsFloat64() + n.SetFloat64(f) + return old +} + +func (n *Number) SwapUint64(u uint64) uint64 { + old := n.AsUint64() + n.SetUint64(u) + return old +} + +// - swap atomic + +func (n *Number) SwapNumberAtomic(nn Number) Number { + return NewNumberFromRaw(atomic.SwapUint64(n.AsRawPtr(), nn.AsRaw())) +} + +func (n *Number) SwapRawAtomic(r uint64) uint64 { + return atomic.SwapUint64(n.AsRawPtr(), r) +} + +func (n *Number) SwapInt64Atomic(i int64) int64 { + return atomic.SwapInt64(n.AsInt64Ptr(), i) +} + +func (n *Number) SwapFloat64Atomic(f float64) float64 { + return rawToFloat64(atomic.SwapUint64(n.AsRawPtr(), float64ToRaw(f))) +} + +func (n *Number) SwapUint64Atomic(u uint64) uint64 { + return atomic.SwapUint64(n.AsUint64Ptr(), u) +} + +// - add + +func (n *Number) AddNumber(kind NumberKind, nn Number) { + switch kind { + case Int64NumberKind: + n.AddInt64(nn.AsInt64()) + case Float64NumberKind: + n.AddFloat64(nn.AsFloat64()) + case Uint64NumberKind: + n.AddUint64(nn.AsUint64()) + } +} + +func (n *Number) AddRaw(kind NumberKind, r uint64) { + n.AddNumber(kind, NewNumberFromRaw(r)) +} + +func (n *Number) AddInt64(i int64) { + *n.AsInt64Ptr() += i +} + +func (n *Number) AddFloat64(f float64) { + *n.AsFloat64Ptr() += f +} + +func (n *Number) AddUint64(u uint64) { + *n.AsUint64Ptr() += u +} + +// - add atomic + +func (n *Number) AddNumberAtomic(kind NumberKind, nn Number) { + switch kind { + case Int64NumberKind: + n.AddInt64Atomic(nn.AsInt64()) + case Float64NumberKind: + n.AddFloat64Atomic(nn.AsFloat64()) + case Uint64NumberKind: + n.AddUint64Atomic(nn.AsUint64()) + } +} + +func (n *Number) AddRawAtomic(kind NumberKind, r uint64) { + n.AddNumberAtomic(kind, NewNumberFromRaw(r)) +} + +func (n *Number) AddInt64Atomic(i int64) { + atomic.AddInt64(n.AsInt64Ptr(), i) +} + +func (n *Number) AddFloat64Atomic(f float64) { + for { + o := n.AsFloat64Atomic() + if n.CompareAndSwapFloat64(o, o+f) { + break + } + } +} + +func (n *Number) AddUint64Atomic(u uint64) { + atomic.AddUint64(n.AsUint64Ptr(), u) +} + +// - compare and swap (atomic only) + +func (n *Number) CompareAndSwapNumber(on, nn Number) bool { + return atomic.CompareAndSwapUint64(n.AsRawPtr(), on.AsRaw(), nn.AsRaw()) +} + +func (n *Number) CompareAndSwapRaw(or, nr uint64) bool { + return atomic.CompareAndSwapUint64(n.AsRawPtr(), or, nr) +} + +func (n *Number) CompareAndSwapInt64(oi, ni int64) bool { + return atomic.CompareAndSwapInt64(n.AsInt64Ptr(), oi, ni) +} + +func (n *Number) CompareAndSwapFloat64(of, nf float64) bool { + return atomic.CompareAndSwapUint64(n.AsRawPtr(), float64ToRaw(of), float64ToRaw(nf)) +} + +func (n *Number) CompareAndSwapUint64(ou, nu uint64) bool { + return atomic.CompareAndSwapUint64(n.AsUint64Ptr(), ou, nu) +} + +// - compare + +// CompareNumber compares two Numbers given their kind. Both numbers +// should have the same kind. This returns: +// 0 if the numbers are equal +// -1 if the subject `n` is less than the argument `nn` +// +1 if the subject `n` is greater than the argument `nn` +func (n Number) CompareNumber(kind NumberKind, nn Number) int { + switch kind { + case Int64NumberKind: + return n.CompareInt64(nn.AsInt64()) + case Float64NumberKind: + return n.CompareFloat64(nn.AsFloat64()) + case Uint64NumberKind: + return n.CompareUint64(nn.AsUint64()) + default: + // you get what you deserve + return 0 + } +} + +// CompareRaw compares two numbers, where one is input as a raw +// uint64, interpreting both values as a `kind` of number. +func (n Number) CompareRaw(kind NumberKind, r uint64) int { + return n.CompareNumber(kind, NewNumberFromRaw(r)) +} + +// CompareInt64 assumes that the Number contains an int64 and performs +// a comparison between the value and the other value. It returns the +// typical result of the compare function: -1 if the value is less +// than the other, 0 if both are equal, 1 if the value is greater than +// the other. +func (n Number) CompareInt64(i int64) int { + this := n.AsInt64() + if this < i { + return -1 + } else if this > i { + return 1 + } + return 0 +} + +// CompareFloat64 assumes that the Number contains a float64 and +// performs a comparison between the value and the other value. It +// returns the typical result of the compare function: -1 if the value +// is less than the other, 0 if both are equal, 1 if the value is +// greater than the other. +func (n Number) CompareFloat64(f float64) int { + this := n.AsFloat64() + if this < f { + return -1 + } else if this > f { + return 1 + } + return 0 +} + +// CompareUint64 assumes that the Number contains an uint64 and performs +// a comparison between the value and the other value. It returns the +// typical result of the compare function: -1 if the value is less +// than the other, 0 if both are equal, 1 if the value is greater than +// the other. +func (n Number) CompareUint64(u uint64) int { + this := n.AsUint64() + if this < u { + return -1 + } else if this > u { + return 1 + } + return 0 +} + +// - relations to zero + +// IsPositive returns true if the actual value is greater than zero. +func (n Number) IsPositive(kind NumberKind) bool { + return n.compareWithZero(kind) > 0 +} + +// IsNegative returns true if the actual value is less than zero. +func (n Number) IsNegative(kind NumberKind) bool { + return n.compareWithZero(kind) < 0 +} + +// IsZero returns true if the actual value is equal to zero. +func (n Number) IsZero(kind NumberKind) bool { + return n.compareWithZero(kind) == 0 +} + +// - misc + +// Emit returns a string representation of the raw value of the +// Number. A %d is used for integral values, %f for floating point +// values. +func (n Number) Emit(kind NumberKind) string { + switch kind { + case Int64NumberKind: + return fmt.Sprintf("%d", n.AsInt64()) + case Float64NumberKind: + return fmt.Sprintf("%f", n.AsFloat64()) + case Uint64NumberKind: + return fmt.Sprintf("%d", n.AsUint64()) + default: + return "" + } +} + +// - private stuff + +func (n Number) compareWithZero(kind NumberKind) int { + switch kind { + case Int64NumberKind: + return n.CompareInt64(0) + case Float64NumberKind: + return n.CompareFloat64(0.) + case Uint64NumberKind: + return n.CompareUint64(0) + default: + // you get what you deserve + return 0 + } +} + +func rawToFloat64(r uint64) float64 { + return math.Float64frombits(r) +} + +func float64ToRaw(f float64) uint64 { + return math.Float64bits(f) +} + +func rawToInt64(r uint64) int64 { + return int64(r) +} + +func int64ToRaw(i int64) uint64 { + return uint64(i) +} + +func rawToUint64(r uint64) uint64 { + return r +} + +func uint64ToRaw(u uint64) uint64 { + return u +} + +func rawPtrToFloat64Ptr(r *uint64) *float64 { + return (*float64)(unsafe.Pointer(r)) +} + +func rawPtrToInt64Ptr(r *uint64) *int64 { + return (*int64)(unsafe.Pointer(r)) +} + +func rawPtrToUint64Ptr(r *uint64) *uint64 { + return r +} diff --git a/api/core/number_test.go b/api/core/number_test.go new file mode 100644 index 00000000000..5efe86296d9 --- /dev/null +++ b/api/core/number_test.go @@ -0,0 +1,148 @@ +// 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 core + +import ( + "testing" + "unsafe" +) + +func TestNumber(t *testing.T) { + iNeg := NewInt64Number(-42) + iZero := NewInt64Number(0) + iPos := NewInt64Number(42) + i64Numbers := [3]Number{iNeg, iZero, iPos} + + for idx, i := range []int64{-42, 0, 42} { + n := i64Numbers[idx] + if got := n.AsInt64(); got != i { + t.Errorf("Number %#v (%s) int64 check failed, expected %d, got %d", n, n.Emit(Int64NumberKind), i, got) + } + } + + for _, n := range i64Numbers { + expected := unsafe.Pointer(&n) + got := unsafe.Pointer(n.AsRawPtr()) + if expected != got { + t.Errorf("Getting raw pointer failed, got %v, expected %v", got, expected) + } + } + + fNeg := NewFloat64Number(-42.) + fZero := NewFloat64Number(0.) + fPos := NewFloat64Number(42.) + f64Numbers := [3]Number{fNeg, fZero, fPos} + + for idx, f := range []float64{-42., 0., 42.} { + n := f64Numbers[idx] + if got := n.AsFloat64(); got != f { + t.Errorf("Number %#v (%s) float64 check failed, expected %f, got %f", n, n.Emit(Int64NumberKind), f, got) + } + } + + for _, n := range f64Numbers { + expected := unsafe.Pointer(&n) + got := unsafe.Pointer(n.AsRawPtr()) + if expected != got { + t.Errorf("Getting raw pointer failed, got %v, expected %v", got, expected) + } + } + + cmpsForNeg := [3]int{0, -1, -1} + cmpsForZero := [3]int{1, 0, -1} + cmpsForPos := [3]int{1, 1, 0} + + type testcase struct { + n Number + kind NumberKind + pos bool + zero bool + neg bool + nums [3]Number + cmps [3]int + } + testcases := []testcase{ + { + n: iNeg, + kind: Int64NumberKind, + pos: false, + zero: false, + neg: true, + nums: i64Numbers, + cmps: cmpsForNeg, + }, + { + n: iZero, + kind: Int64NumberKind, + pos: false, + zero: true, + neg: false, + nums: i64Numbers, + cmps: cmpsForZero, + }, + { + n: iPos, + kind: Int64NumberKind, + pos: true, + zero: false, + neg: false, + nums: i64Numbers, + cmps: cmpsForPos, + }, + { + n: fNeg, + kind: Float64NumberKind, + pos: false, + zero: false, + neg: true, + nums: f64Numbers, + cmps: cmpsForNeg, + }, + { + n: fZero, + kind: Float64NumberKind, + pos: false, + zero: true, + neg: false, + nums: f64Numbers, + cmps: cmpsForZero, + }, + { + n: fPos, + kind: Float64NumberKind, + pos: true, + zero: false, + neg: false, + nums: f64Numbers, + cmps: cmpsForPos, + }, + } + for _, tt := range testcases { + if got := tt.n.IsPositive(tt.kind); got != tt.pos { + t.Errorf("Number %#v (%s) positive check failed, expected %v, got %v", tt.n, tt.n.Emit(tt.kind), tt.pos, got) + } + if got := tt.n.IsZero(tt.kind); got != tt.zero { + t.Errorf("Number %#v (%s) zero check failed, expected %v, got %v", tt.n, tt.n.Emit(tt.kind), tt.pos, got) + } + if got := tt.n.IsNegative(tt.kind); got != tt.neg { + t.Errorf("Number %#v (%s) negative check failed, expected %v, got %v", tt.n, tt.n.Emit(tt.kind), tt.pos, got) + } + for i := 0; i < 3; i++ { + if got := tt.n.CompareRaw(tt.kind, tt.nums[i].AsRaw()); got != tt.cmps[i] { + t.Errorf("Number %#v (%s) compare check with %#v (%s) failed, expected %d, got %d", tt.n, tt.n.Emit(tt.kind), tt.nums[i], tt.nums[i].Emit(tt.kind), tt.cmps[i], got) + } + } + } +} diff --git a/api/core/numberkind_string.go b/api/core/numberkind_string.go new file mode 100644 index 00000000000..be3eea9be36 --- /dev/null +++ b/api/core/numberkind_string.go @@ -0,0 +1,25 @@ +// Code generated by "stringer -type=NumberKind"; DO NOT EDIT. + +package core + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[Int64NumberKind-0] + _ = x[Float64NumberKind-1] + _ = x[Uint64NumberKind-2] +} + +const _NumberKind_name = "Int64NumberKindFloat64NumberKindUint64NumberKind" + +var _NumberKind_index = [...]uint8{0, 15, 32, 48} + +func (i NumberKind) String() string { + if i < 0 || i >= NumberKind(len(_NumberKind_index)-1) { + return "NumberKind(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _NumberKind_name[_NumberKind_index[i]:_NumberKind_index[i+1]] +} diff --git a/api/metric/api.go b/api/metric/api.go index ba41f3e616a..27fb20a9a4e 100644 --- a/api/metric/api.go +++ b/api/metric/api.go @@ -21,41 +21,9 @@ import ( "go.opentelemetry.io/api/unit" ) -// Instrument is the implementation-level interface Set/Add/Record -// individual metrics without precomputed labels. -type Instrument interface { - // AcquireHandle creates a Handle to record metrics with - // precomputed labels. - AcquireHandle(labels LabelSet) Handle - // RecordOne allows the SDK to observe a single metric event. - RecordOne(ctx context.Context, value MeasurementValue, labels LabelSet) -} - -// Handle is the implementation-level interface to Set/Add/Record -// individual metrics with precomputed labels. -type Handle interface { - // RecordOne allows the SDK to observe a single metric event. - RecordOne(ctx context.Context, value MeasurementValue) - // Release frees the resources associated with this handle. It - // does not affect the metric this handle was created through. - Release() -} - -// TODO this belongs outside the metrics API, in some sense, but that -// might create a dependency. Putting this here means we can't re-use -// a LabelSet between metrics and tracing, even when they are the same -// SDK. - -// TODO(krnowak): I wonder if this should just be: -// -// type LabelSet interface{} -// -// Not sure how the Meter function is useful. - // LabelSet is an implementation-level interface that represents a // []core.KeyValue for use as pre-defined labels in the metrics API. type LabelSet interface { - Meter() Meter } // Options contains some options for metrics of any kind. @@ -108,27 +76,29 @@ type MeasureOptionApplier interface { // Measurement is used for reporting a batch of metric // values. Instances of this type should be created by instruments -// (Int64Counter.Measurement()). +// (e.g., Int64Counter.Measurement()). type Measurement struct { - instrument Instrument - value MeasurementValue + instrument InstrumentImpl + number core.Number } -// Instrument returns an instrument that created this measurement. -func (m Measurement) Instrument() Instrument { +// Instrument returns the instrument that created this measurement. +// This returns an implementation-level object for use by the SDK, +// users should not refer to this. +func (m Measurement) InstrumentImpl() InstrumentImpl { return m.instrument } -// Value returns a value recorded in this measurement. -func (m Measurement) Value() MeasurementValue { - return m.value +// Number returns a number recorded in this measurement. +func (m Measurement) Number() core.Number { + return m.number } // Meter is an interface to the metrics portion of the OpenTelemetry SDK. type Meter interface { // Labels returns a reference to a set of labels that cannot // be read by the application. - Labels(context.Context, ...core.KeyValue) LabelSet + Labels(...core.KeyValue) LabelSet // NewInt64Counter creates a new integral counter with a given // name and customized with passed options. diff --git a/api/metric/api_test.go b/api/metric/api_test.go index d4f29da63a7..6de55447b4d 100644 --- a/api/metric/api_test.go +++ b/api/metric/api_test.go @@ -370,25 +370,25 @@ func TestCounter(t *testing.T) { meter := newMockMeter() c := meter.NewFloat64Counter("ajwaj") ctx := context.Background() - labels := meter.Labels(ctx) + labels := meter.Labels() c.Add(ctx, 42, labels) handle := c.AcquireHandle(labels) handle.Add(ctx, 42) meter.RecordBatch(ctx, labels, c.Measurement(42)) t.Log("Testing float counter") - checkBatches(t, ctx, labels, meter, Float64ValueKind, c.instrument) + checkBatches(t, ctx, labels, meter, core.Float64NumberKind, c.instrument) } { meter := newMockMeter() c := meter.NewInt64Counter("ajwaj") ctx := context.Background() - labels := meter.Labels(ctx) + labels := meter.Labels() c.Add(ctx, 42, labels) handle := c.AcquireHandle(labels) handle.Add(ctx, 42) meter.RecordBatch(ctx, labels, c.Measurement(42)) t.Log("Testing int counter") - checkBatches(t, ctx, labels, meter, Int64ValueKind, c.instrument) + checkBatches(t, ctx, labels, meter, core.Int64NumberKind, c.instrument) } } @@ -397,25 +397,25 @@ func TestGauge(t *testing.T) { meter := newMockMeter() g := meter.NewFloat64Gauge("ajwaj") ctx := context.Background() - labels := meter.Labels(ctx) + labels := meter.Labels() g.Set(ctx, 42, labels) handle := g.AcquireHandle(labels) handle.Set(ctx, 42) meter.RecordBatch(ctx, labels, g.Measurement(42)) t.Log("Testing float gauge") - checkBatches(t, ctx, labels, meter, Float64ValueKind, g.instrument) + checkBatches(t, ctx, labels, meter, core.Float64NumberKind, g.instrument) } { meter := newMockMeter() g := meter.NewInt64Gauge("ajwaj") ctx := context.Background() - labels := meter.Labels(ctx) + labels := meter.Labels() g.Set(ctx, 42, labels) handle := g.AcquireHandle(labels) handle.Set(ctx, 42) meter.RecordBatch(ctx, labels, g.Measurement(42)) t.Log("Testing int gauge") - checkBatches(t, ctx, labels, meter, Int64ValueKind, g.instrument) + checkBatches(t, ctx, labels, meter, core.Int64NumberKind, g.instrument) } } @@ -424,29 +424,29 @@ func TestMeasure(t *testing.T) { meter := newMockMeter() m := meter.NewFloat64Measure("ajwaj") ctx := context.Background() - labels := meter.Labels(ctx) + labels := meter.Labels() m.Record(ctx, 42, labels) handle := m.AcquireHandle(labels) handle.Record(ctx, 42) meter.RecordBatch(ctx, labels, m.Measurement(42)) t.Log("Testing float measure") - checkBatches(t, ctx, labels, meter, Float64ValueKind, m.instrument) + checkBatches(t, ctx, labels, meter, core.Float64NumberKind, m.instrument) } { meter := newMockMeter() m := meter.NewInt64Measure("ajwaj") ctx := context.Background() - labels := meter.Labels(ctx) + labels := meter.Labels() m.Record(ctx, 42, labels) handle := m.AcquireHandle(labels) handle.Record(ctx, 42) meter.RecordBatch(ctx, labels, m.Measurement(42)) t.Log("Testing int measure") - checkBatches(t, ctx, labels, meter, Int64ValueKind, m.instrument) + checkBatches(t, ctx, labels, meter, core.Int64NumberKind, m.instrument) } } -func checkBatches(t *testing.T, ctx context.Context, labels LabelSet, meter *mockMeter, kind ValueKind, instrument Instrument) { +func checkBatches(t *testing.T, ctx context.Context, labels LabelSet, meter *mockMeter, kind core.NumberKind, instrument InstrumentImpl) { t.Helper() if len(meter.measurementBatches) != 3 { t.Errorf("Expected 3 recorded measurement batches, got %d", len(meter.measurementBatches)) @@ -487,20 +487,20 @@ func checkBatches(t *testing.T, ctx context.Context, labels LabelSet, meter *moc t.Errorf("Wrong recorded instrument in measurement %d in batch %d, expected %s, got %s", j, i, d(ourInstrument), d(measurement.instrument)) } ft := fortyTwo(t, kind) - if measurement.value.RawCompare(ft.AsRaw(), kind) != 0 { - t.Errorf("Wrong recorded value in measurement %d in batch %d, expected %s, got %s", j, i, ft.Emit(kind), measurement.value.Emit(kind)) + if measurement.number.CompareNumber(kind, ft) != 0 { + t.Errorf("Wrong recorded value in measurement %d in batch %d, expected %s, got %s", j, i, ft.Emit(kind), measurement.number.Emit(kind)) } } } } -func fortyTwo(t *testing.T, kind ValueKind) MeasurementValue { +func fortyTwo(t *testing.T, kind core.NumberKind) core.Number { switch kind { - case Int64ValueKind: - return NewInt64MeasurementValue(42) - case Float64ValueKind: - return NewFloat64MeasurementValue(42) + case core.Int64NumberKind: + return core.NewInt64Number(42) + case core.Float64NumberKind: + return core.NewFloat64Number(42) } t.Errorf("Invalid value kind %q", kind) - return NewInt64MeasurementValue(0) + return core.NewInt64Number(0) } diff --git a/api/metric/common.go b/api/metric/common.go index 1e91e3ebf4f..d6f13d4de0f 100644 --- a/api/metric/common.go +++ b/api/metric/common.go @@ -16,14 +16,16 @@ package metric import ( "context" + + "go.opentelemetry.io/api/core" ) type commonMetric struct { - instrument Instrument + instrument InstrumentImpl } type commonHandle struct { - handle Handle + handle HandleImpl } func (m commonMetric) acquireCommonHandle(labels LabelSet) commonHandle { @@ -31,40 +33,44 @@ func (m commonMetric) acquireCommonHandle(labels LabelSet) commonHandle { } func (m commonMetric) float64Measurement(value float64) Measurement { - return newMeasurement(m.instrument, NewFloat64MeasurementValue(value)) + return newMeasurement(m.instrument, core.NewFloat64Number(value)) } func (m commonMetric) int64Measurement(value int64) Measurement { - return newMeasurement(m.instrument, NewInt64MeasurementValue(value)) + return newMeasurement(m.instrument, core.NewInt64Number(value)) +} + +func (m commonMetric) recordOne(ctx context.Context, number core.Number, labels LabelSet) { + m.instrument.RecordOne(ctx, number, labels) } -func (m commonMetric) recordOne(ctx context.Context, value MeasurementValue, labels LabelSet) { - m.instrument.RecordOne(ctx, value, labels) +func (m commonMetric) Impl() InstrumentImpl { + return m.instrument } -func (h commonHandle) recordOne(ctx context.Context, value MeasurementValue) { - h.handle.RecordOne(ctx, value) +func (h commonHandle) recordOne(ctx context.Context, number core.Number) { + h.handle.RecordOne(ctx, number) } func (h commonHandle) Release() { h.handle.Release() } -func newCommonMetric(instrument Instrument) commonMetric { +func newCommonMetric(instrument InstrumentImpl) commonMetric { return commonMetric{ instrument: instrument, } } -func newCommonHandle(handle Handle) commonHandle { +func newCommonHandle(handle HandleImpl) commonHandle { return commonHandle{ handle: handle, } } -func newMeasurement(instrument Instrument, value MeasurementValue) Measurement { +func newMeasurement(instrument InstrumentImpl, number core.Number) Measurement { return Measurement{ instrument: instrument, - value: value, + number: number, } } diff --git a/api/metric/counter.go b/api/metric/counter.go index 0f9231da79d..315714be9de 100644 --- a/api/metric/counter.go +++ b/api/metric/counter.go @@ -16,6 +16,8 @@ package metric import ( "context" + + "go.opentelemetry.io/api/core" ) // Float64Counter is a metric that accumulates float64 values. @@ -86,7 +88,7 @@ func (c *Int64Counter) Measurement(value int64) Measurement { // counter with the WithKeys option, then the missing value will be // treated as unspecified. func (c *Float64Counter) Add(ctx context.Context, value float64, labels LabelSet) { - c.recordOne(ctx, NewFloat64MeasurementValue(value), labels) + c.recordOne(ctx, core.NewFloat64Number(value), labels) } // Add adds the value to the counter's sum. The labels should contain @@ -97,15 +99,15 @@ func (c *Float64Counter) Add(ctx context.Context, value float64, labels LabelSet // counter with the WithKeys option, then the missing value will be // treated as unspecified. func (c *Int64Counter) Add(ctx context.Context, value int64, labels LabelSet) { - c.recordOne(ctx, NewInt64MeasurementValue(value), labels) + c.recordOne(ctx, core.NewInt64Number(value), labels) } // Add adds the value to the counter's sum. func (h *Float64CounterHandle) Add(ctx context.Context, value float64) { - h.recordOne(ctx, NewFloat64MeasurementValue(value)) + h.recordOne(ctx, core.NewFloat64Number(value)) } // Add adds the value to the counter's sum. func (h *Int64CounterHandle) Add(ctx context.Context, value int64) { - h.recordOne(ctx, NewInt64MeasurementValue(value)) + h.recordOne(ctx, core.NewInt64Number(value)) } diff --git a/api/metric/gauge.go b/api/metric/gauge.go index 0cc964a86b3..a643b591fa0 100644 --- a/api/metric/gauge.go +++ b/api/metric/gauge.go @@ -16,6 +16,8 @@ package metric import ( "context" + + "go.opentelemetry.io/api/core" ) // Float64Gauge is a metric that stores the last float64 value. @@ -86,7 +88,7 @@ func (g *Int64Gauge) Measurement(value int64) Measurement { // gauge with the WithKeys option, then the missing value will be // treated as unspecified. func (g *Float64Gauge) Set(ctx context.Context, value float64, labels LabelSet) { - g.recordOne(ctx, NewFloat64MeasurementValue(value), labels) + g.recordOne(ctx, core.NewFloat64Number(value), labels) } // Set assigns the passed value to the value of the gauge. The labels @@ -97,15 +99,15 @@ func (g *Float64Gauge) Set(ctx context.Context, value float64, labels LabelSet) // gauge with the WithKeys option, then the missing value will be // treated as unspecified. func (g *Int64Gauge) Set(ctx context.Context, value int64, labels LabelSet) { - g.recordOne(ctx, NewInt64MeasurementValue(value), labels) + g.recordOne(ctx, core.NewInt64Number(value), labels) } // Set assigns the passed value to the value of the gauge. func (h *Float64GaugeHandle) Set(ctx context.Context, value float64) { - h.recordOne(ctx, NewFloat64MeasurementValue(value)) + h.recordOne(ctx, core.NewFloat64Number(value)) } // Set assigns the passed value to the value of the gauge. func (h *Int64GaugeHandle) Set(ctx context.Context, value int64) { - h.recordOne(ctx, NewInt64MeasurementValue(value)) + h.recordOne(ctx, core.NewInt64Number(value)) } diff --git a/api/metric/measure.go b/api/metric/measure.go index e3c2328a2ea..faf4e35cbf6 100644 --- a/api/metric/measure.go +++ b/api/metric/measure.go @@ -16,6 +16,8 @@ package metric import ( "context" + + "go.opentelemetry.io/api/core" ) // Float64Measure is a metric that records float64 values. @@ -86,7 +88,7 @@ func (c *Int64Measure) Measurement(value int64) Measurement { // measure with the WithKeys option, then the missing value will be // treated as unspecified. func (c *Float64Measure) Record(ctx context.Context, value float64, labels LabelSet) { - c.recordOne(ctx, NewFloat64MeasurementValue(value), labels) + c.recordOne(ctx, core.NewFloat64Number(value), labels) } // Record adds a new value to the list of measure's records. The @@ -97,15 +99,15 @@ func (c *Float64Measure) Record(ctx context.Context, value float64, labels Label // measure with the WithKeys option, then the missing value will be // treated as unspecified. func (c *Int64Measure) Record(ctx context.Context, value int64, labels LabelSet) { - c.recordOne(ctx, NewInt64MeasurementValue(value), labels) + c.recordOne(ctx, core.NewInt64Number(value), labels) } // Record adds a new value to the list of measure's records. func (h *Float64MeasureHandle) Record(ctx context.Context, value float64) { - h.recordOne(ctx, NewFloat64MeasurementValue(value)) + h.recordOne(ctx, core.NewFloat64Number(value)) } // Record adds a new value to the list of measure's records. func (h *Int64MeasureHandle) Record(ctx context.Context, value int64) { - h.recordOne(ctx, NewInt64MeasurementValue(value)) + h.recordOne(ctx, core.NewInt64Number(value)) } diff --git a/api/metric/mock_test.go b/api/metric/mock_test.go index 5c375c4cab0..161b45ba357 100644 --- a/api/metric/mock_test.go +++ b/api/metric/mock_test.go @@ -27,10 +27,10 @@ type ( } mockInstrument struct { - name string - kind mockKind - valueKind ValueKind - opts Options + name string + kind mockKind + numberKind core.NumberKind + opts Options } mockLabelSet struct { @@ -52,15 +52,15 @@ type ( mockMeasurement struct { instrument *mockInstrument - value MeasurementValue + number core.Number } ) var ( - _ Instrument = &mockInstrument{} - _ Handle = &mockHandle{} - _ LabelSet = &mockLabelSet{} - _ Meter = &mockMeter{} + _ InstrumentImpl = &mockInstrument{} + _ HandleImpl = &mockHandle{} + _ LabelSet = &mockLabelSet{} + _ Meter = &mockMeter{} ) const ( @@ -69,28 +69,28 @@ const ( mockKindMeasure ) -func (i *mockInstrument) AcquireHandle(labels LabelSet) Handle { +func (i *mockInstrument) AcquireHandle(labels LabelSet) HandleImpl { return &mockHandle{ instrument: i, labelSet: labels.(*mockLabelSet), } } -func (i *mockInstrument) RecordOne(ctx context.Context, value MeasurementValue, labels LabelSet) { - doRecordBatch(labels.(*mockLabelSet), ctx, i, value) +func (i *mockInstrument) RecordOne(ctx context.Context, number core.Number, labels LabelSet) { + doRecordBatch(labels.(*mockLabelSet), ctx, i, number) } -func (h *mockHandle) RecordOne(ctx context.Context, value MeasurementValue) { - doRecordBatch(h.labelSet, ctx, h.instrument, value) +func (h *mockHandle) RecordOne(ctx context.Context, number core.Number) { + doRecordBatch(h.labelSet, ctx, h.instrument, number) } func (h *mockHandle) Release() { } -func doRecordBatch(labelSet *mockLabelSet, ctx context.Context, instrument *mockInstrument, value MeasurementValue) { +func doRecordBatch(labelSet *mockLabelSet, ctx context.Context, instrument *mockInstrument, number core.Number) { labelSet.meter.recordMockBatch(ctx, labelSet, mockMeasurement{ instrument: instrument, - value: value, + number: number, }) } @@ -102,7 +102,7 @@ func newMockMeter() *mockMeter { return &mockMeter{} } -func (m *mockMeter) Labels(ctx context.Context, labels ...core.KeyValue) LabelSet { +func (m *mockMeter) Labels(labels ...core.KeyValue) LabelSet { ul := make(map[core.Key]core.Value) for _, kv := range labels { ul[kv.Key] = kv.Value @@ -114,65 +114,65 @@ func (m *mockMeter) Labels(ctx context.Context, labels ...core.KeyValue) LabelSe } func (m *mockMeter) NewInt64Counter(name string, cos ...CounterOptionApplier) Int64Counter { - instrument := m.newCounterInstrument(name, Int64ValueKind, cos...) + instrument := m.newCounterInstrument(name, core.Int64NumberKind, cos...) return WrapInt64CounterInstrument(instrument) } func (m *mockMeter) NewFloat64Counter(name string, cos ...CounterOptionApplier) Float64Counter { - instrument := m.newCounterInstrument(name, Float64ValueKind, cos...) + instrument := m.newCounterInstrument(name, core.Float64NumberKind, cos...) return WrapFloat64CounterInstrument(instrument) } -func (m *mockMeter) newCounterInstrument(name string, valueKind ValueKind, cos ...CounterOptionApplier) *mockInstrument { +func (m *mockMeter) newCounterInstrument(name string, numberKind core.NumberKind, cos ...CounterOptionApplier) *mockInstrument { opts := Options{} ApplyCounterOptions(&opts, cos...) return &mockInstrument{ - name: name, - kind: mockKindCounter, - valueKind: valueKind, - opts: opts, + name: name, + kind: mockKindCounter, + numberKind: numberKind, + opts: opts, } } func (m *mockMeter) NewInt64Gauge(name string, gos ...GaugeOptionApplier) Int64Gauge { - instrument := m.newGaugeInstrument(name, Int64ValueKind, gos...) + instrument := m.newGaugeInstrument(name, core.Int64NumberKind, gos...) return WrapInt64GaugeInstrument(instrument) } func (m *mockMeter) NewFloat64Gauge(name string, gos ...GaugeOptionApplier) Float64Gauge { - instrument := m.newGaugeInstrument(name, Float64ValueKind, gos...) + instrument := m.newGaugeInstrument(name, core.Float64NumberKind, gos...) return WrapFloat64GaugeInstrument(instrument) } -func (m *mockMeter) newGaugeInstrument(name string, valueKind ValueKind, gos ...GaugeOptionApplier) *mockInstrument { +func (m *mockMeter) newGaugeInstrument(name string, numberKind core.NumberKind, gos ...GaugeOptionApplier) *mockInstrument { opts := Options{} ApplyGaugeOptions(&opts, gos...) return &mockInstrument{ - name: name, - kind: mockKindGauge, - valueKind: valueKind, - opts: opts, + name: name, + kind: mockKindGauge, + numberKind: numberKind, + opts: opts, } } func (m *mockMeter) NewInt64Measure(name string, mos ...MeasureOptionApplier) Int64Measure { - instrument := m.newMeasureInstrument(name, Int64ValueKind, mos...) + instrument := m.newMeasureInstrument(name, core.Int64NumberKind, mos...) return WrapInt64MeasureInstrument(instrument) } func (m *mockMeter) NewFloat64Measure(name string, mos ...MeasureOptionApplier) Float64Measure { - instrument := m.newMeasureInstrument(name, Float64ValueKind, mos...) + instrument := m.newMeasureInstrument(name, core.Float64NumberKind, mos...) return WrapFloat64MeasureInstrument(instrument) } -func (m *mockMeter) newMeasureInstrument(name string, valueKind ValueKind, mos ...MeasureOptionApplier) *mockInstrument { +func (m *mockMeter) newMeasureInstrument(name string, numberKind core.NumberKind, mos ...MeasureOptionApplier) *mockInstrument { opts := Options{} ApplyMeasureOptions(&opts, mos...) return &mockInstrument{ - name: name, - kind: mockKindMeasure, - valueKind: valueKind, - opts: opts, + name: name, + kind: mockKindMeasure, + numberKind: numberKind, + opts: opts, } } @@ -182,8 +182,8 @@ func (m *mockMeter) RecordBatch(ctx context.Context, labels LabelSet, measuremen for i := 0; i < len(measurements); i++ { m := measurements[i] mm[i] = mockMeasurement{ - instrument: m.Instrument().(*mockInstrument), - value: m.Value(), + instrument: m.InstrumentImpl().(*mockInstrument), + number: m.Number(), } } m.recordMockBatch(ctx, ourLabelSet, mm...) diff --git a/api/metric/noop.go b/api/metric/noop.go index 5741f2cd396..f68cc8c9179 100644 --- a/api/metric/noop.go +++ b/api/metric/noop.go @@ -12,28 +12,28 @@ type noopLabelSet struct{} type noopInstrument struct{} var _ Meter = noopMeter{} -var _ Instrument = noopInstrument{} -var _ Handle = noopHandle{} +var _ InstrumentImpl = noopInstrument{} +var _ HandleImpl = noopHandle{} var _ LabelSet = noopLabelSet{} -func (noopHandle) RecordOne(context.Context, MeasurementValue) { +func (noopHandle) RecordOne(context.Context, core.Number) { } func (noopHandle) Release() { } -func (noopInstrument) AcquireHandle(LabelSet) Handle { +func (noopInstrument) AcquireHandle(LabelSet) HandleImpl { return noopHandle{} } -func (noopInstrument) RecordOne(context.Context, MeasurementValue, LabelSet) { +func (noopInstrument) RecordOne(context.Context, core.Number, LabelSet) { } -func (noopLabelSet) Meter() Meter { +func (noopInstrument) Meter() Meter { return noopMeter{} } -func (noopMeter) Labels(context.Context, ...core.KeyValue) LabelSet { +func (noopMeter) Labels(...core.KeyValue) LabelSet { return noopLabelSet{} } diff --git a/api/metric/sdkhelpers.go b/api/metric/sdkhelpers.go index 0a31823c8b7..3fbc19b5ea5 100644 --- a/api/metric/sdkhelpers.go +++ b/api/metric/sdkhelpers.go @@ -14,11 +14,39 @@ package metric +import ( + "context" + + "go.opentelemetry.io/api/core" +) + +// InstrumentImpl is the implementation-level interface Set/Add/Record +// individual metrics without precomputed labels. +type InstrumentImpl interface { + // AcquireHandle creates a Handle to record metrics with + // precomputed labels. + AcquireHandle(labels LabelSet) HandleImpl + + // RecordOne allows the SDK to observe a single metric event. + RecordOne(ctx context.Context, number core.Number, labels LabelSet) +} + +// HandleImpl is the implementation-level interface to Set/Add/Record +// individual metrics with precomputed labels. +type HandleImpl interface { + // RecordOne allows the SDK to observe a single metric event. + RecordOne(ctx context.Context, number core.Number) + + // Release frees the resources associated with this handle. It + // does not affect the metric this handle was created through. + Release() +} + // WrapInt64CounterInstrument wraps the instrument in the type-safe // wrapper as an integral counter. // // It is mostly intended for SDKs. -func WrapInt64CounterInstrument(instrument Instrument) Int64Counter { +func WrapInt64CounterInstrument(instrument InstrumentImpl) Int64Counter { return Int64Counter{commonMetric: newCommonMetric(instrument)} } @@ -26,7 +54,7 @@ func WrapInt64CounterInstrument(instrument Instrument) Int64Counter { // wrapper as an floating point counter. // // It is mostly intended for SDKs. -func WrapFloat64CounterInstrument(instrument Instrument) Float64Counter { +func WrapFloat64CounterInstrument(instrument InstrumentImpl) Float64Counter { return Float64Counter{commonMetric: newCommonMetric(instrument)} } @@ -34,7 +62,7 @@ func WrapFloat64CounterInstrument(instrument Instrument) Float64Counter { // wrapper as an integral gauge. // // It is mostly intended for SDKs. -func WrapInt64GaugeInstrument(instrument Instrument) Int64Gauge { +func WrapInt64GaugeInstrument(instrument InstrumentImpl) Int64Gauge { return Int64Gauge{commonMetric: newCommonMetric(instrument)} } @@ -42,7 +70,7 @@ func WrapInt64GaugeInstrument(instrument Instrument) Int64Gauge { // wrapper as an floating point gauge. // // It is mostly intended for SDKs. -func WrapFloat64GaugeInstrument(instrument Instrument) Float64Gauge { +func WrapFloat64GaugeInstrument(instrument InstrumentImpl) Float64Gauge { return Float64Gauge{commonMetric: newCommonMetric(instrument)} } @@ -50,7 +78,7 @@ func WrapFloat64GaugeInstrument(instrument Instrument) Float64Gauge { // wrapper as an integral measure. // // It is mostly intended for SDKs. -func WrapInt64MeasureInstrument(instrument Instrument) Int64Measure { +func WrapInt64MeasureInstrument(instrument InstrumentImpl) Int64Measure { return Int64Measure{commonMetric: newCommonMetric(instrument)} } @@ -58,7 +86,7 @@ func WrapInt64MeasureInstrument(instrument Instrument) Int64Measure { // wrapper as an floating point measure. // // It is mostly intended for SDKs. -func WrapFloat64MeasureInstrument(instrument Instrument) Float64Measure { +func WrapFloat64MeasureInstrument(instrument InstrumentImpl) Float64Measure { return Float64Measure{commonMetric: newCommonMetric(instrument)} } diff --git a/api/metric/value.go b/api/metric/value.go deleted file mode 100644 index 4550ceb501a..00000000000 --- a/api/metric/value.go +++ /dev/null @@ -1,185 +0,0 @@ -// 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 metric - -import ( - "fmt" - "math" -) - -//go:generate stringer -type=ValueKind - -// ValueKind describes the data type of the measurement value the -// metric generates. -type ValueKind int8 - -const ( - // Int64ValueKind means that the metric generates values of - // type int64. - Int64ValueKind ValueKind = iota - // Float64ValueKind means that the metric generates values of - // type float64. - Float64ValueKind -) - -// MeasurementValue represents either an integral or a floating point -// value of a measurement. It needs to be accompanied with a value -// kind or some source that provides a value kind describing this -// measurement value. -type MeasurementValue uint64 - -// NewInt64MeasurementValue creates an integral MeasurementValue. -func NewInt64MeasurementValue(i int64) MeasurementValue { - return newFromRaw(int64ToRaw(i)) -} - -// NewFloat64MeasurementValue creates a floating point -// MeasurementValue. -func NewFloat64MeasurementValue(f float64) MeasurementValue { - return newFromRaw(float64ToRaw(f)) -} - -func newFromRaw(raw uint64) MeasurementValue { - return MeasurementValue(raw) -} - -// AsInt64 assumes that the measurement value contains an int64 and -// returns it as such. Make sure that the accompanying source of value -// kind indeed tells you its a 64 bit integral measurement value, -// otherwise the returned int64 will be wrong. -func (v MeasurementValue) AsInt64() int64 { - return rawToInt64(v.AsRaw()) -} - -// AsFloat64 assumes that the measurement value contains a float64 and -// returns it as such. Make sure that the accompanying source of value -// kind indeed tells you its a 64 bit floating point measurement -// value, otherwise the returned float64 will be wrong. -func (v MeasurementValue) AsFloat64() float64 { - return rawToFloat64(v.AsRaw()) -} - -// AsRaw gets the raw, uninterpreted value of the measurement. Might -// be useful for some atomic operations. -func (v MeasurementValue) AsRaw() uint64 { - return uint64(v) -} - -// AsRawPtr gets the pointer to the raw, uninterpreted value of the -// measurement. Might be useful for some atomic operations. -func (v *MeasurementValue) AsRawPtr() *uint64 { - return (*uint64)(v) -} - -// Emit returns a string representation of the actual value of the -// MeasurementValue. A %d is used for integral values, %f for floating -// point values. -func (v MeasurementValue) Emit(kind ValueKind) string { - switch kind { - case Int64ValueKind: - return fmt.Sprintf("%d", v.AsInt64()) - case Float64ValueKind: - return fmt.Sprintf("%f", v.AsFloat64()) - default: - return "" - } -} - -// Float64Compare assumes that the MeasurementValue contains a float64 -// and performs a comparison between the value and the other value. It -// returns the typical result of the compare function: -1 if the value -// is less than the other, 0 if both are equal, 1 if the value is -// greater than the other. -func (v MeasurementValue) Float64Compare(other float64) int { - this := v.AsFloat64() - if this < other { - return -1 - } else if this > other { - return 1 - } - return 0 -} - -// Int64Compare assumes that the MeasurementValue contains an int64 -// and performs a comparison between the value and the other value. It -// returns the typical result of the compare function: -1 if the value -// is less than the other, 0 if both are equal, 1 if the value is -// greater than the other. -func (v MeasurementValue) Int64Compare(other int64) int { - this := v.AsInt64() - if this < other { - return -1 - } else if this > other { - return 1 - } - return 0 -} - -// RawCompare calls either Float64Compare or Int64Compare, depending -// on the passed kind. -func (v MeasurementValue) RawCompare(other uint64, kind ValueKind) int { - switch kind { - case Int64ValueKind: - return v.Int64Compare(rawToInt64(other)) - case Float64ValueKind: - return v.Float64Compare(rawToFloat64(other)) - default: - // you get what you deserve - return 0 - } -} - -// IsPositive returns true if the actual value is greater than zero. -func (v MeasurementValue) IsPositive(kind ValueKind) bool { - return v.compareWithZero(kind) > 0 -} - -// IsNegative returns true if the actual value is less than zero. -func (v MeasurementValue) IsNegative(kind ValueKind) bool { - return v.compareWithZero(kind) < 0 -} - -// IsZero returns true if the actual value is equal to zero. -func (v MeasurementValue) IsZero(kind ValueKind) bool { - return v.compareWithZero(kind) == 0 -} - -func (v MeasurementValue) compareWithZero(kind ValueKind) int { - switch kind { - case Int64ValueKind: - return v.Int64Compare(0) - case Float64ValueKind: - return v.Float64Compare(0.) - default: - // you get what you deserve - return 0 - } -} - -func rawToFloat64(r uint64) float64 { - return math.Float64frombits(r) -} - -func float64ToRaw(f float64) uint64 { - return math.Float64bits(f) -} - -func rawToInt64(r uint64) int64 { - return int64(r) -} - -func int64ToRaw(i int64) uint64 { - return uint64(i) -} diff --git a/api/metric/value_test.go b/api/metric/value_test.go deleted file mode 100644 index 3b017818175..00000000000 --- a/api/metric/value_test.go +++ /dev/null @@ -1,148 +0,0 @@ -// 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 metric - -import ( - "testing" - "unsafe" -) - -func TestMeasurementValue(t *testing.T) { - iNeg := NewInt64MeasurementValue(-42) - iZero := NewInt64MeasurementValue(0) - iPos := NewInt64MeasurementValue(42) - i64Values := [3]MeasurementValue{iNeg, iZero, iPos} - - for idx, i := range []int64{-42, 0, 42} { - v := i64Values[idx] - if got := v.AsInt64(); got != i { - t.Errorf("Value %#v (%s) int64 check failed, expected %d, got %d", v, v.Emit(Int64ValueKind), i, got) - } - } - - for _, v := range i64Values { - expected := unsafe.Pointer(&v) - got := unsafe.Pointer(v.AsRawPtr()) - if expected != got { - t.Errorf("Getting raw pointer failed, got %v, expected %v", got, expected) - } - } - - fNeg := NewFloat64MeasurementValue(-42.) - fZero := NewFloat64MeasurementValue(0.) - fPos := NewFloat64MeasurementValue(42.) - f64Values := [3]MeasurementValue{fNeg, fZero, fPos} - - for idx, f := range []float64{-42., 0., 42.} { - v := f64Values[idx] - if got := v.AsFloat64(); got != f { - t.Errorf("Value %#v (%s) float64 check failed, expected %f, got %f", v, v.Emit(Int64ValueKind), f, got) - } - } - - for _, v := range f64Values { - expected := unsafe.Pointer(&v) - got := unsafe.Pointer(v.AsRawPtr()) - if expected != got { - t.Errorf("Getting raw pointer failed, got %v, expected %v", got, expected) - } - } - - cmpsForNeg := [3]int{0, -1, -1} - cmpsForZero := [3]int{1, 0, -1} - cmpsForPos := [3]int{1, 1, 0} - - type testcase struct { - v MeasurementValue - kind ValueKind - pos bool - zero bool - neg bool - vals [3]MeasurementValue - cmps [3]int - } - testcases := []testcase{ - { - v: iNeg, - kind: Int64ValueKind, - pos: false, - zero: false, - neg: true, - vals: i64Values, - cmps: cmpsForNeg, - }, - { - v: iZero, - kind: Int64ValueKind, - pos: false, - zero: true, - neg: false, - vals: i64Values, - cmps: cmpsForZero, - }, - { - v: iPos, - kind: Int64ValueKind, - pos: true, - zero: false, - neg: false, - vals: i64Values, - cmps: cmpsForPos, - }, - { - v: fNeg, - kind: Float64ValueKind, - pos: false, - zero: false, - neg: true, - vals: f64Values, - cmps: cmpsForNeg, - }, - { - v: fZero, - kind: Float64ValueKind, - pos: false, - zero: true, - neg: false, - vals: f64Values, - cmps: cmpsForZero, - }, - { - v: fPos, - kind: Float64ValueKind, - pos: true, - zero: false, - neg: false, - vals: f64Values, - cmps: cmpsForPos, - }, - } - for _, tt := range testcases { - if got := tt.v.IsPositive(tt.kind); got != tt.pos { - t.Errorf("Value %#v (%s) positive check failed, expected %v, got %v", tt.v, tt.v.Emit(tt.kind), tt.pos, got) - } - if got := tt.v.IsZero(tt.kind); got != tt.zero { - t.Errorf("Value %#v (%s) zero check failed, expected %v, got %v", tt.v, tt.v.Emit(tt.kind), tt.pos, got) - } - if got := tt.v.IsNegative(tt.kind); got != tt.neg { - t.Errorf("Value %#v (%s) negative check failed, expected %v, got %v", tt.v, tt.v.Emit(tt.kind), tt.pos, got) - } - for i := 0; i < 3; i++ { - if got := tt.v.RawCompare(tt.vals[i].AsRaw(), tt.kind); got != tt.cmps[i] { - t.Errorf("Value %#v (%s) compare check with %#v (%s) failed, expected %d, got %d", tt.v, tt.v.Emit(tt.kind), tt.vals[i], tt.vals[i].Emit(tt.kind), tt.cmps[i], got) - } - } - } -} diff --git a/api/metric/valuekind_string.go b/api/metric/valuekind_string.go deleted file mode 100644 index 499cd3027a2..00000000000 --- a/api/metric/valuekind_string.go +++ /dev/null @@ -1,24 +0,0 @@ -// Code generated by "stringer -type=ValueKind"; DO NOT EDIT. - -package metric - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[Int64ValueKind-0] - _ = x[Float64ValueKind-1] -} - -const _ValueKind_name = "Int64ValueKindFloat64ValueKind" - -var _ValueKind_index = [...]uint8{0, 14, 30} - -func (i ValueKind) String() string { - if i < 0 || i >= ValueKind(len(_ValueKind_index)-1) { - return "ValueKind(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _ValueKind_name[_ValueKind_index[i]:_ValueKind_index[i+1]] -} diff --git a/example/basic/go.sum b/example/basic/go.sum index 18886dd8fee..0d402fd076a 100644 --- a/example/basic/go.sum +++ b/example/basic/go.sum @@ -1,5 +1,6 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7/go.mod h1:Q5DbzQ+3AkgGwymQO7aZFNP7ns2lZKGtvRBzRXfdi60= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= @@ -81,6 +82,7 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= @@ -256,7 +258,7 @@ golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20190911151314-feee8acb394c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190930201159-7c411dea38b0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191015211201-9c6d90b5a7d0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191025174333-e96d959c4788/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= diff --git a/example/basic/main.go b/example/basic/main.go index c051acf86ca..1571e2229df 100644 --- a/example/basic/main.go +++ b/example/basic/main.go @@ -50,7 +50,7 @@ func main() { distributedcontext.Insert(barKey.String("bar1")), ) - commonLabels := meter.Labels(ctx, lemonsKey.Int(10)) + commonLabels := meter.Labels(lemonsKey.Int(10)) gauge := oneMetric.AcquireHandle(commonLabels) defer gauge.Release() diff --git a/example/http-stackdriver/go.sum b/example/http-stackdriver/go.sum index 9bef660c978..5ed9e6b7ade 100644 --- a/example/http-stackdriver/go.sum +++ b/example/http-stackdriver/go.sum @@ -15,6 +15,7 @@ cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiy dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7/go.mod h1:Q5DbzQ+3AkgGwymQO7aZFNP7ns2lZKGtvRBzRXfdi60= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= @@ -100,6 +101,7 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -330,7 +332,7 @@ golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20190930201159-7c411dea38b0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191010171213-8abd42400456/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191015211201-9c6d90b5a7d0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191025174333-e96d959c4788/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= diff --git a/example/http/go.sum b/example/http/go.sum index 51514f5c601..39eb2dd97d7 100644 --- a/example/http/go.sum +++ b/example/http/go.sum @@ -1,5 +1,6 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7/go.mod h1:Q5DbzQ+3AkgGwymQO7aZFNP7ns2lZKGtvRBzRXfdi60= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= @@ -82,6 +83,7 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= @@ -265,7 +267,7 @@ golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20190911151314-feee8acb394c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190930201159-7c411dea38b0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191015211201-9c6d90b5a7d0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191025174333-e96d959c4788/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= diff --git a/example/jaeger/go.sum b/example/jaeger/go.sum index a5f2b27e697..e3a41702368 100644 --- a/example/jaeger/go.sum +++ b/example/jaeger/go.sum @@ -2,6 +2,7 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7/go.mod h1:Q5DbzQ+3AkgGwymQO7aZFNP7ns2lZKGtvRBzRXfdi60= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= @@ -86,6 +87,7 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= @@ -282,7 +284,7 @@ golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20190911151314-feee8acb394c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190930201159-7c411dea38b0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191015211201-9c6d90b5a7d0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191025174333-e96d959c4788/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.11.0 h1:n/qM3q0/rV2F0pox7o0CvNhlPvZAo7pLbef122cbLJ0= diff --git a/example/namedtracer/go.sum b/example/namedtracer/go.sum index 93816165f1d..789dc1b0023 100644 --- a/example/namedtracer/go.sum +++ b/example/namedtracer/go.sum @@ -1,5 +1,6 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7/go.mod h1:Q5DbzQ+3AkgGwymQO7aZFNP7ns2lZKGtvRBzRXfdi60= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= @@ -81,6 +82,7 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= @@ -257,7 +259,7 @@ golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20190911151314-feee8acb394c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190930201159-7c411dea38b0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191015211201-9c6d90b5a7d0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191025174333-e96d959c4788/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= diff --git a/exporter/trace/jaeger/go.sum b/exporter/trace/jaeger/go.sum index b662f0d5ee4..c4f158b3f96 100644 --- a/exporter/trace/jaeger/go.sum +++ b/exporter/trace/jaeger/go.sum @@ -2,6 +2,7 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7/go.mod h1:Q5DbzQ+3AkgGwymQO7aZFNP7ns2lZKGtvRBzRXfdi60= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= @@ -87,6 +88,7 @@ github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= @@ -283,7 +285,7 @@ golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20190911151314-feee8acb394c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190930201159-7c411dea38b0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191015211201-9c6d90b5a7d0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191025174333-e96d959c4788/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.11.0 h1:n/qM3q0/rV2F0pox7o0CvNhlPvZAo7pLbef122cbLJ0= diff --git a/exporter/trace/stackdriver/go.sum b/exporter/trace/stackdriver/go.sum index 3e06f122612..d4a9495302b 100644 --- a/exporter/trace/stackdriver/go.sum +++ b/exporter/trace/stackdriver/go.sum @@ -14,6 +14,7 @@ cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiy dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7/go.mod h1:Q5DbzQ+3AkgGwymQO7aZFNP7ns2lZKGtvRBzRXfdi60= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= @@ -99,6 +100,7 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -328,7 +330,7 @@ golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20190930201159-7c411dea38b0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191010171213-8abd42400456/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191015211201-9c6d90b5a7d0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191025174333-e96d959c4788/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= diff --git a/go.mod b/go.mod index 69594bf6516..22984ae0a2c 100644 --- a/go.mod +++ b/go.mod @@ -3,12 +3,14 @@ module go.opentelemetry.io go 1.13 require ( + github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7 github.com/client9/misspell v0.3.4 github.com/gogo/protobuf v1.3.1 // indirect github.com/golangci/gocyclo v0.0.0-20180528144436-0a533e8fa43d // indirect github.com/golangci/golangci-lint v1.21.0 github.com/golangci/revgrep v0.0.0-20180812185044-276a5c0a1039 // indirect github.com/google/go-cmp v0.3.1 + github.com/google/gofuzz v1.0.0 // indirect github.com/gostaticanalysis/analysisutil v0.0.3 // indirect github.com/hashicorp/golang-lru v0.5.3 github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect @@ -19,9 +21,10 @@ require ( github.com/securego/gosec v0.0.0-20191008095658-28c1128b7336 // indirect github.com/spf13/afero v1.2.2 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/stretchr/testify v1.4.0 github.com/uudashr/gocognit v1.0.0 // indirect golang.org/x/sys v0.0.0-20191010194322-b09406accb47 // indirect - golang.org/x/tools v0.0.0-20191015211201-9c6d90b5a7d0 + golang.org/x/tools v0.0.0-20191025174333-e96d959c4788 google.golang.org/genproto v0.0.0-20190925194540-b8fbc687dcfb // indirect google.golang.org/grpc v1.24.0 mvdan.cc/unparam v0.0.0-20190917161559-b83a221c10a2 // indirect diff --git a/go.sum b/go.sum index c6430265d89..cadf1b265e7 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7 h1:qELHH0AWCvf98Yf+CNIJx9vOZOfHFDDzgDRYsnNk/vs= +github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7/go.mod h1:Q5DbzQ+3AkgGwymQO7aZFNP7ns2lZKGtvRBzRXfdi60= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OpenPeeDeeP/depguard v1.0.1 h1:VlW4R6jmBIv3/u1JNlawEvJMM4J+dPORPaZasQee8Us= github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM= @@ -117,6 +119,8 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3 h1:JVnpOZS+qxli+rgVl98ILOXVNbW+kb5wcxeGx8ShUIw= @@ -342,8 +346,8 @@ golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20190911151314-feee8acb394c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190930201159-7c411dea38b0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191015211201-9c6d90b5a7d0 h1:KPtnBRFVP7cXAlZnb0bT/gohhmrkpXkk8DP9eZ+Aius= -golang.org/x/tools v0.0.0-20191015211201-9c6d90b5a7d0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191025174333-e96d959c4788 h1:V1TVsT556gJ/CGE0EoWtjLu1VXDAAmFz1AuGKPpn1Q0= +golang.org/x/tools v0.0.0-20191025174333-e96d959c4788/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= diff --git a/sdk/export/exporter.go b/sdk/export/exporter.go index caf409553df..54c6aa785f5 100644 --- a/sdk/export/exporter.go +++ b/sdk/export/exporter.go @@ -38,3 +38,26 @@ type SpanSyncer interface { type SpanBatcher interface { ExportSpans(context.Context, []*SpanData) } + +// MetricBatcher is responsible for deciding which kind of aggregation +// to use and gathering exported results from the SDK. The standard SDK +// supports binding only one of these interfaces, i.e., a single exporter. +// +// Multiple-exporters could be implemented by implementing this interface +// for a group of MetricBatcher. +type MetricBatcher interface { + // AggregatorFor should return the kind of aggregator + // suited to the requested export. Returning `nil` + // indicates to ignore the metric update. + // + // Note: This is context-free because the handle should not be + // bound to the incoming context. This call should not block. + AggregatorFor(MetricRecord) MetricAggregator + + // Export receives pairs of records and aggregators + // during the SDK Collect(). Exporter implementations + // must access the specific aggregator to receive the + // exporter data, since the format of the data varies + // by aggregation. + Export(context.Context, MetricRecord, MetricAggregator) +} diff --git a/sdk/export/metric.go b/sdk/export/metric.go new file mode 100644 index 00000000000..7de3cfbd149 --- /dev/null +++ b/sdk/export/metric.go @@ -0,0 +1,117 @@ +// 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 export + +import ( + "context" + + "go.opentelemetry.io/api/core" + "go.opentelemetry.io/api/unit" +) + +// MetricAggregator implements a specific aggregation behavior, e.g., +// a counter, a gauge, a histogram. +type MetricAggregator interface { + // Update receives a new measured value and incorporates it + // into the aggregation. + Update(context.Context, core.Number, MetricRecord) + + // Collect is called during the SDK Collect() to + // finish one period of aggregation. Collect() is + // called in a single-threaded context. Update() + // calls may arrive concurrently. + Collect(context.Context, MetricRecord, MetricBatcher) +} + +// MetricRecord is the unit of export, pairing a metric +// instrument and set of labels. +type MetricRecord interface { + // Descriptor() describes the metric instrument. + Descriptor() *Descriptor + + // Labels() describe the labsels corresponding the + // aggregation being performed. + Labels() []core.KeyValue +} + +// MetricKind describes the kind of instrument. +type MetricKind int8 + +const ( + CounterMetricKind MetricKind = iota + GaugeMetricKind + MeasureMetricKind +) + +// Descriptor describes a metric instrument to the exporter. +type Descriptor struct { + name string + metricKind MetricKind + keys []core.Key + description string + unit unit.Unit + numberKind core.NumberKind + alternate bool +} + +// NewDescriptor builds a new descriptor, for use by `Meter` +// implementations. +func NewDescriptor( + name string, + metricKind MetricKind, + keys []core.Key, + description string, + unit unit.Unit, + numberKind core.NumberKind, + alternate bool, +) *Descriptor { + return &Descriptor{ + name: name, + metricKind: metricKind, + keys: keys, + description: description, + unit: unit, + numberKind: numberKind, + alternate: alternate, + } +} + +func (d *Descriptor) Name() string { + return d.name +} + +func (d *Descriptor) MetricKind() MetricKind { + return d.metricKind +} + +func (d *Descriptor) Keys() []core.Key { + return d.keys +} + +func (d *Descriptor) Description() string { + return d.description +} + +func (d *Descriptor) Unit() unit.Unit { + return d.unit +} + +func (d *Descriptor) NumberKind() core.NumberKind { + return d.numberKind +} + +func (d *Descriptor) Alternate() bool { + return d.alternate +} diff --git a/sdk/metric/aggregator/counter/counter.go b/sdk/metric/aggregator/counter/counter.go new file mode 100644 index 00000000000..58d7cee3c69 --- /dev/null +++ b/sdk/metric/aggregator/counter/counter.go @@ -0,0 +1,66 @@ +// 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 counter + +import ( + "context" + + "go.opentelemetry.io/api/core" + "go.opentelemetry.io/sdk/export" +) + +// Aggregator aggregates counter events. +type Aggregator struct { + // current holds current increments to this counter record + current core.Number + + // checkpoint is a temporary used during Collect() + checkpoint core.Number +} + +var _ export.MetricAggregator = &Aggregator{} + +// New returns a new counter aggregator. This aggregator computes an +// atomic sum. +func New() *Aggregator { + return &Aggregator{} +} + +// AsNumber returns the accumulated count as an int64. +func (c *Aggregator) AsNumber() core.Number { + return c.checkpoint.AsNumber() +} + +// Collect checkpoints the current value (atomically) and exports it. +func (c *Aggregator) Collect(ctx context.Context, rec export.MetricRecord, exp export.MetricBatcher) { + desc := rec.Descriptor() + kind := desc.NumberKind() + zero := core.NewZeroNumber(kind) + c.checkpoint = c.current.SwapNumberAtomic(zero) + + exp.Export(ctx, rec, c) +} + +// Update modifies the current value (atomically) for later export. +func (c *Aggregator) Update(_ context.Context, number core.Number, rec export.MetricRecord) { + desc := rec.Descriptor() + kind := desc.NumberKind() + if !desc.Alternate() && number.IsNegative(kind) { + // TODO warn + return + } + + c.current.AddNumberAtomic(kind, number) +} diff --git a/sdk/metric/aggregator/counter/counter_test.go b/sdk/metric/aggregator/counter/counter_test.go new file mode 100644 index 00000000000..45e381b0d39 --- /dev/null +++ b/sdk/metric/aggregator/counter/counter_test.go @@ -0,0 +1,93 @@ +// 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 counter + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/api/core" + "go.opentelemetry.io/sdk/export" + "go.opentelemetry.io/sdk/metric/aggregator/test" +) + +const count = 100 + +func TestCounterMonotonic(t *testing.T) { + ctx := context.Background() + + test.RunProfiles(t, func(t *testing.T, profile test.Profile) { + agg := New() + + batcher, record := test.NewAggregatorTest(export.CounterMetricKind, profile.NumberKind, false) + + sum := core.Number(0) + for i := 0; i < count; i++ { + x := profile.Random(+1) + sum.AddNumber(profile.NumberKind, x) + agg.Update(ctx, x, record) + } + + agg.Collect(ctx, record, batcher) + + require.Equal(t, sum, agg.AsNumber(), "Same sum - monotonic") + }) +} + +func TestCounterMonotonicNegative(t *testing.T) { + ctx := context.Background() + + test.RunProfiles(t, func(t *testing.T, profile test.Profile) { + agg := New() + + batcher, record := test.NewAggregatorTest(export.CounterMetricKind, profile.NumberKind, false) + + for i := 0; i < count; i++ { + agg.Update(ctx, profile.Random(-1), record) + } + + sum := profile.Random(+1) + agg.Update(ctx, sum, record) + agg.Collect(ctx, record, batcher) + + require.Equal(t, sum, agg.AsNumber(), "Same sum - monotonic") + }) +} + +func TestCounterNonMonotonic(t *testing.T) { + ctx := context.Background() + + test.RunProfiles(t, func(t *testing.T, profile test.Profile) { + agg := New() + + batcher, record := test.NewAggregatorTest(export.CounterMetricKind, profile.NumberKind, true) + + sum := core.Number(0) + for i := 0; i < count; i++ { + x := profile.Random(+1) + y := profile.Random(-1) + sum.AddNumber(profile.NumberKind, x) + sum.AddNumber(profile.NumberKind, y) + agg.Update(ctx, x, record) + agg.Update(ctx, y, record) + } + + agg.Collect(ctx, record, batcher) + + require.Equal(t, sum, agg.AsNumber(), "Same sum - monotonic") + }) +} diff --git a/sdk/metric/aggregator/ddsketch/ddsketch.go b/sdk/metric/aggregator/ddsketch/ddsketch.go new file mode 100644 index 00000000000..fd2a85facc7 --- /dev/null +++ b/sdk/metric/aggregator/ddsketch/ddsketch.go @@ -0,0 +1,103 @@ +// 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 ddsketch + +import ( + "context" + "sync" + + sdk "github.com/DataDog/sketches-go/ddsketch" + + "go.opentelemetry.io/api/core" + "go.opentelemetry.io/sdk/export" +) + +// Aggregator aggregates measure events. +type Aggregator struct { + lock sync.Mutex + cfg *sdk.Config + kind core.NumberKind + current *sdk.DDSketch + checkpoint *sdk.DDSketch +} + +var _ export.MetricAggregator = &Aggregator{} + +// New returns a new DDSketch aggregator. +func New(cfg *sdk.Config, desc *export.Descriptor) *Aggregator { + return &Aggregator{ + cfg: cfg, + kind: desc.NumberKind(), + current: sdk.NewDDSketch(cfg), + } +} + +// NewDefaultConfig returns a new, default DDSketch config. +func NewDefaultConfig() *sdk.Config { + return sdk.NewDefaultConfig() +} + +// Sum returns the sum of the checkpoint. +func (c *Aggregator) Sum() float64 { + return c.checkpoint.Sum() +} + +// Count returns the count of the checkpoint. +func (c *Aggregator) Count() int64 { + return c.checkpoint.Count() +} + +// Max returns the max of the checkpoint. +func (c *Aggregator) Max() float64 { + return c.checkpoint.Quantile(1) +} + +// Min returns the min of the checkpoint. +func (c *Aggregator) Min() float64 { + return c.checkpoint.Quantile(0) +} + +// Quantile returns the estimated quantile of the checkpoint. +func (c *Aggregator) Quantile(q float64) float64 { + return c.checkpoint.Quantile(q) +} + +// Collect checkpoints the current value (atomically) and exports it. +func (c *Aggregator) Collect(ctx context.Context, rec export.MetricRecord, exp export.MetricBatcher) { + replace := sdk.NewDDSketch(c.cfg) + + c.lock.Lock() + c.checkpoint = c.current + c.current = replace + c.lock.Unlock() + + if c.checkpoint.Count() != 0 { + exp.Export(ctx, rec, c) + } +} + +// Update modifies the current value (atomically) for later export. +func (c *Aggregator) Update(_ context.Context, number core.Number, rec export.MetricRecord) { + desc := rec.Descriptor() + kind := desc.NumberKind() + + if !desc.Alternate() && number.IsNegative(kind) { + // TODO warn + return + } + + c.lock.Lock() + defer c.lock.Unlock() + c.current.Add(number.CoerceToFloat64(kind)) +} diff --git a/sdk/metric/aggregator/ddsketch/ddsketch_test.go b/sdk/metric/aggregator/ddsketch/ddsketch_test.go new file mode 100644 index 00000000000..74b9bf32ac4 --- /dev/null +++ b/sdk/metric/aggregator/ddsketch/ddsketch_test.go @@ -0,0 +1,67 @@ +// 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 ddsketch + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/sdk/export" + "go.opentelemetry.io/sdk/metric/aggregator/test" +) + +const count = 100 + +// N.B. DDSketch only supports absolute measures + +func TestDDSketchAbsolute(t *testing.T) { + ctx := context.Background() + + test.RunProfiles(t, func(t *testing.T, profile test.Profile) { + batcher, record := test.NewAggregatorTest(export.MeasureMetricKind, profile.NumberKind, false) + + agg := New(NewDefaultConfig(), record.Descriptor()) + + var all test.Numbers + for i := 0; i < count; i++ { + x := profile.Random(+1) + all = append(all, x) + agg.Update(ctx, x, record) + } + + agg.Collect(ctx, record, batcher) + + all.Sort() + + require.InEpsilon(t, + all.Sum(profile.NumberKind).CoerceToFloat64(profile.NumberKind), + agg.Sum(), + 0.0000001, + "Same sum - absolute") + require.Equal(t, all.Count(), agg.Count(), "Same count - absolute") + require.Equal(t, + all[len(all)-1].CoerceToFloat64(profile.NumberKind), + agg.Max(), + "Same max - absolute") + // Median + require.InEpsilon(t, + all[len(all)/2].CoerceToFloat64(profile.NumberKind), + agg.Quantile(0.5), + 0.1, + "Same median - absolute") + }) +} diff --git a/sdk/metric/aggregator/gauge/gauge.go b/sdk/metric/aggregator/gauge/gauge.go new file mode 100644 index 00000000000..b6f03cb801f --- /dev/null +++ b/sdk/metric/aggregator/gauge/gauge.go @@ -0,0 +1,127 @@ +// 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 gauge + +import ( + "context" + "sync/atomic" + "time" + "unsafe" + + "go.opentelemetry.io/api/core" + "go.opentelemetry.io/sdk/export" +) + +// Note: This aggregator enforces the behavior of monotonic gauges to +// the best of its ability, but it will not retain any memory of +// infrequently used gauges. Exporters may wish to enforce this, or +// they may simply treat monotonic as a semantic hint. + +type ( + + // Aggregator aggregates gauge events. + Aggregator struct { + // data is an atomic pointer to *gaugeData. It is set + // to `nil` if the gauge has not been set since the + // last collection. + current unsafe.Pointer + + // N.B. Export is not called when checkpoint is nil + checkpoint unsafe.Pointer + } + + // gaugeData stores the current value of a gauge along with + // a sequence number to determine the winner of a race. + gaugeData struct { + // value is the int64- or float64-encoded Set() data + value core.Number + + // timestamp indicates when this record was submitted. + // this can be used to pick a winner when multiple + // records contain gauge data for the same labels due + // to races. + timestamp time.Time + } +) + +var _ export.MetricAggregator = &Aggregator{} + +// An unset gauge has zero timestamp and zero value. +var unsetGauge = &gaugeData{} + +// New returns a new gauge aggregator. This aggregator retains the +// last value and timestamp that were recorded. +func New() *Aggregator { + return &Aggregator{ + current: unsafe.Pointer(unsetGauge), + checkpoint: unsafe.Pointer(unsetGauge), + } +} + +// AsNumber returns the recorded gauge value as an int64. +func (g *Aggregator) AsNumber() core.Number { + return (*gaugeData)(g.checkpoint).value.AsNumber() +} + +// Timestamp returns the timestamp of the alst recorded gauge value. +func (g *Aggregator) Timestamp() time.Time { + return (*gaugeData)(g.checkpoint).timestamp +} + +// Collect checkpoints the current value (atomically) and exports it. +func (g *Aggregator) Collect(ctx context.Context, rec export.MetricRecord, exp export.MetricBatcher) { + g.checkpoint = atomic.LoadPointer(&g.current) + + exp.Export(ctx, rec, g) +} + +// Update modifies the current value (atomically) for later export. +func (g *Aggregator) Update(_ context.Context, number core.Number, rec export.MetricRecord) { + desc := rec.Descriptor() + if !desc.Alternate() { + g.updateNonMonotonic(number) + } else { + g.updateMonotonic(number, desc) + } +} + +func (g *Aggregator) updateNonMonotonic(number core.Number) { + ngd := &gaugeData{ + value: number, + timestamp: time.Now(), + } + atomic.StorePointer(&g.current, unsafe.Pointer(ngd)) +} + +func (g *Aggregator) updateMonotonic(number core.Number, desc *export.Descriptor) { + ngd := &gaugeData{ + timestamp: time.Now(), + value: number, + } + kind := desc.NumberKind() + + for { + gd := (*gaugeData)(atomic.LoadPointer(&g.current)) + + if gd.value.CompareNumber(kind, number) > 0 { + // TODO warn + return + } + + if atomic.CompareAndSwapPointer(&g.current, unsafe.Pointer(gd), unsafe.Pointer(ngd)) { + return + } + } +} diff --git a/sdk/metric/aggregator/gauge/gauge_test.go b/sdk/metric/aggregator/gauge/gauge_test.go new file mode 100644 index 00000000000..dd54776142f --- /dev/null +++ b/sdk/metric/aggregator/gauge/gauge_test.go @@ -0,0 +1,96 @@ +// 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 gauge + +import ( + "context" + "math/rand" + "testing" + + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/api/core" + "go.opentelemetry.io/sdk/export" + "go.opentelemetry.io/sdk/metric/aggregator/test" +) + +const count = 100 + +var _ export.MetricAggregator = &Aggregator{} + +func TestGaugeNonMonotonic(t *testing.T) { + ctx := context.Background() + + test.RunProfiles(t, func(t *testing.T, profile test.Profile) { + agg := New() + + batcher, record := test.NewAggregatorTest(export.GaugeMetricKind, profile.NumberKind, false) + + var last core.Number + for i := 0; i < count; i++ { + x := profile.Random(rand.Intn(1)*2 - 1) + last = x + agg.Update(ctx, x, record) + } + + agg.Collect(ctx, record, batcher) + + require.Equal(t, last, agg.AsNumber(), "Same last value - non-monotonic") + }) +} + +func TestGaugeMonotonic(t *testing.T) { + ctx := context.Background() + + test.RunProfiles(t, func(t *testing.T, profile test.Profile) { + agg := New() + + batcher, record := test.NewAggregatorTest(export.GaugeMetricKind, profile.NumberKind, true) + + small := profile.Random(+1) + last := small + for i := 0; i < count; i++ { + x := profile.Random(+1) + last.AddNumber(profile.NumberKind, x) + agg.Update(ctx, last, record) + } + + agg.Collect(ctx, record, batcher) + + require.Equal(t, last, agg.AsNumber(), "Same last value - monotonic") + }) +} + +func TestGaugeMonotonicDescending(t *testing.T) { + ctx := context.Background() + + test.RunProfiles(t, func(t *testing.T, profile test.Profile) { + agg := New() + + batcher, record := test.NewAggregatorTest(export.GaugeMetricKind, profile.NumberKind, true) + + first := profile.Random(+1) + agg.Update(ctx, first, record) + + for i := 0; i < count; i++ { + x := profile.Random(-1) + agg.Update(ctx, x, record) + } + + agg.Collect(ctx, record, batcher) + + require.Equal(t, first, agg.AsNumber(), "Same last value - monotonic") + }) +} diff --git a/sdk/metric/aggregator/maxsumcount/msc.go b/sdk/metric/aggregator/maxsumcount/msc.go new file mode 100644 index 00000000000..b549a311536 --- /dev/null +++ b/sdk/metric/aggregator/maxsumcount/msc.go @@ -0,0 +1,103 @@ +// 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 maxsumcount + +import ( + "context" + + "go.opentelemetry.io/api/core" + "go.opentelemetry.io/sdk/export" +) + +type ( + // Aggregator aggregates measure events, keeping only the max, + // sum, and count. + Aggregator struct { + live state + save state + } + + state struct { + count core.Number + sum core.Number + max core.Number + } +) + +var _ export.MetricAggregator = &Aggregator{} + +// New returns a new measure aggregator for computing max, sum, and count. +func New() *Aggregator { + return &Aggregator{} +} + +// Sum returns the accumulated sum as a Number. +func (c *Aggregator) Sum() core.Number { + return c.save.sum +} + +// Count returns the accumulated count. +func (c *Aggregator) Count() int64 { + return int64(c.save.count.AsUint64()) +} + +// Max returns the accumulated max as a Number. +func (c *Aggregator) Max() core.Number { + return c.save.max +} + +// Collect saves the current value (atomically) and exports it. +func (c *Aggregator) Collect(ctx context.Context, rec export.MetricRecord, exp export.MetricBatcher) { + desc := rec.Descriptor() + kind := desc.NumberKind() + zero := core.NewZeroNumber(kind) + + // N.B. There is no atomic operation that can update all three + // values at once, so there are races between Update() and + // Collect(). Therefore, atomically swap fields independently, + // knowing that individually the three parts of this aggregation + // could be spread across multiple collections in rare cases. + + c.save.count.SetUint64(c.live.count.SwapUint64Atomic(0)) + c.save.sum = c.live.sum.SwapNumberAtomic(zero) + c.save.max = c.live.max.SwapNumberAtomic(zero) + + exp.Export(ctx, rec, c) +} + +// Update modifies the current value (atomically) for later export. +func (c *Aggregator) Update(_ context.Context, number core.Number, rec export.MetricRecord) { + desc := rec.Descriptor() + kind := desc.NumberKind() + + if !desc.Alternate() && number.IsNegative(kind) { + // TODO warn + return + } + + c.live.count.AddUint64Atomic(1) + c.live.sum.AddNumberAtomic(kind, number) + + for { + current := c.live.max.AsNumberAtomic() + + if number.CompareNumber(kind, current) <= 0 { + break + } + if c.live.max.CompareAndSwapNumber(current, number) { + break + } + } +} diff --git a/sdk/metric/aggregator/maxsumcount/msc_test.go b/sdk/metric/aggregator/maxsumcount/msc_test.go new file mode 100644 index 00000000000..85893247479 --- /dev/null +++ b/sdk/metric/aggregator/maxsumcount/msc_test.go @@ -0,0 +1,59 @@ +// 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 maxsumcount + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/sdk/export" + "go.opentelemetry.io/sdk/metric/aggregator/test" +) + +const count = 100 + +func TestMaxSumCountAbsolute(t *testing.T) { + ctx := context.Background() + + test.RunProfiles(t, func(t *testing.T, profile test.Profile) { + batcher, record := test.NewAggregatorTest(export.MeasureMetricKind, profile.NumberKind, false) + + agg := New() + + var all test.Numbers + for i := 0; i < count; i++ { + x := profile.Random(+1) + all = append(all, x) + agg.Update(ctx, x, record) + } + + agg.Collect(ctx, record, batcher) + + all.Sort() + + require.InEpsilon(t, + all.Sum(profile.NumberKind).CoerceToFloat64(profile.NumberKind), + agg.Sum().CoerceToFloat64(profile.NumberKind), + 0.000000001, + "Same sum - absolute") + require.Equal(t, all.Count(), agg.Count(), "Same sum - absolute") + require.Equal(t, + all[len(all)-1], + agg.Max(), + "Same sum - absolute") + }) +} diff --git a/sdk/metric/aggregator/test/test.go b/sdk/metric/aggregator/test/test.go new file mode 100644 index 00000000000..4dcb133777c --- /dev/null +++ b/sdk/metric/aggregator/test/test.go @@ -0,0 +1,103 @@ +// 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 test + +import ( + "context" + "math/rand" + "sort" + "testing" + + "go.opentelemetry.io/api/core" + "go.opentelemetry.io/sdk/export" +) + +var _ export.MetricBatcher = &metricBatcher{} +var _ export.MetricRecord = &metricRecord{} + +type Profile struct { + NumberKind core.NumberKind + Random func(sign int) core.Number +} + +var profiles = []Profile{ + { + NumberKind: core.Int64NumberKind, + Random: func(sign int) core.Number { + return core.NewInt64Number(int64(sign) * int64(rand.Intn(100000))) + }, + }, + { + NumberKind: core.Float64NumberKind, + Random: func(sign int) core.Number { + return core.NewFloat64Number(float64(sign) * rand.Float64() * 100000) + }, + }, +} + +type metricBatcher struct { +} + +type metricRecord struct { + descriptor *export.Descriptor +} + +func NewAggregatorTest(mkind export.MetricKind, nkind core.NumberKind, alternate bool) (export.MetricBatcher, export.MetricRecord) { + desc := export.NewDescriptor("test.name", mkind, nil, "", "", nkind, alternate) + return &metricBatcher{}, &metricRecord{descriptor: desc} +} + +func (t *metricRecord) Descriptor() *export.Descriptor { + return t.descriptor +} + +func (t *metricRecord) Labels() []core.KeyValue { + return nil +} + +func (m *metricBatcher) AggregatorFor(rec export.MetricRecord) export.MetricAggregator { + return nil +} + +func (m *metricBatcher) Export(context.Context, export.MetricRecord, export.MetricAggregator) { +} + +func RunProfiles(t *testing.T, f func(*testing.T, Profile)) { + for _, profile := range profiles { + t.Run(profile.NumberKind.String(), func(t *testing.T) { + f(t, profile) + }) + } +} + +type Numbers []core.Number + +func (n *Numbers) Sort() { + sort.Slice(*n, func(i, j int) bool { + return (*n)[i] < (*n)[j] + }) +} + +func (n *Numbers) Sum(kind core.NumberKind) core.Number { + var sum core.Number + for _, num := range *n { + sum.AddNumber(kind, num) + } + return sum +} + +func (n *Numbers) Count() int64 { + return int64(len(*n)) +} diff --git a/sdk/metric/benchmark_test.go b/sdk/metric/benchmark_test.go new file mode 100644 index 00000000000..eb99c93314e --- /dev/null +++ b/sdk/metric/benchmark_test.go @@ -0,0 +1,412 @@ +// 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 metric_test + +import ( + "context" + "fmt" + "math/rand" + "strings" + "testing" + + "go.opentelemetry.io/api/core" + "go.opentelemetry.io/api/key" + "go.opentelemetry.io/sdk/export" + sdk "go.opentelemetry.io/sdk/metric" + "go.opentelemetry.io/sdk/metric/aggregator/counter" + "go.opentelemetry.io/sdk/metric/aggregator/ddsketch" + "go.opentelemetry.io/sdk/metric/aggregator/gauge" + "go.opentelemetry.io/sdk/metric/aggregator/maxsumcount" +) + +type benchFixture struct { + sdk *sdk.SDK + B *testing.B +} + +func newFixture(b *testing.B) *benchFixture { + b.ReportAllocs() + bf := &benchFixture{ + B: b, + } + bf.sdk = sdk.New(bf) + return bf +} + +func (bf *benchFixture) AggregatorFor(rec export.MetricRecord) export.MetricAggregator { + switch rec.Descriptor().MetricKind() { + case export.CounterMetricKind: + return counter.New() + case export.GaugeMetricKind: + return gauge.New() + case export.MeasureMetricKind: + if strings.HasSuffix(rec.Descriptor().Name(), "maxsumcount") { + return maxsumcount.New() + } else if strings.HasSuffix(rec.Descriptor().Name(), "ddsketch") { + return ddsketch.New(ddsketch.NewDefaultConfig(), rec.Descriptor()) + } + } + return nil +} + +func (bf *benchFixture) Export(ctx context.Context, rec export.MetricRecord, agg export.MetricAggregator) { +} + +func makeLabels(n int) []core.KeyValue { + used := map[string]bool{} + l := make([]core.KeyValue, n) + for i := 0; i < n; i++ { + var k string + for { + k = fmt.Sprint("k", rand.Intn(1000000000)) + if !used[k] { + used[k] = true + break + } + } + l[i] = key.New(k).String(fmt.Sprint("v", rand.Intn(1000000000))) + } + return l +} + +func benchmarkLabels(b *testing.B, n int) { + fix := newFixture(b) + labs := makeLabels(n) + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + fix.sdk.Labels(labs...) + } +} + +func BenchmarkLabels_1(b *testing.B) { + benchmarkLabels(b, 1) +} + +func BenchmarkLabels_2(b *testing.B) { + benchmarkLabels(b, 2) +} + +func BenchmarkLabels_4(b *testing.B) { + benchmarkLabels(b, 4) +} + +func BenchmarkLabels_8(b *testing.B) { + benchmarkLabels(b, 8) +} + +func BenchmarkLabels_16(b *testing.B) { + benchmarkLabels(b, 16) +} + +// Note: performance does not depend on label set size for the +// benchmarks below. + +func BenchmarkInt64CounterAdd(b *testing.B) { + ctx := context.Background() + fix := newFixture(b) + labs := fix.sdk.Labels(makeLabels(1)...) + cnt := fix.sdk.NewInt64Counter("int64.counter") + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + cnt.Add(ctx, 1, labs) + } +} + +func BenchmarkInt64CounterAcquireHandle(b *testing.B) { + fix := newFixture(b) + labs := fix.sdk.Labels(makeLabels(1)...) + cnt := fix.sdk.NewInt64Counter("int64.counter") + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + handle := cnt.AcquireHandle(labs) + handle.Release() + } +} + +func BenchmarkInt64CounterHandleAdd(b *testing.B) { + ctx := context.Background() + fix := newFixture(b) + labs := fix.sdk.Labels(makeLabels(1)...) + cnt := fix.sdk.NewInt64Counter("int64.counter") + handle := cnt.AcquireHandle(labs) + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + handle.Add(ctx, 1) + } +} + +func BenchmarkFloat64CounterAdd(b *testing.B) { + ctx := context.Background() + fix := newFixture(b) + labs := fix.sdk.Labels(makeLabels(1)...) + cnt := fix.sdk.NewFloat64Counter("float64.counter") + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + cnt.Add(ctx, 1.1, labs) + } +} + +func BenchmarkFloat64CounterAcquireHandle(b *testing.B) { + fix := newFixture(b) + labs := fix.sdk.Labels(makeLabels(1)...) + cnt := fix.sdk.NewFloat64Counter("float64.counter") + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + handle := cnt.AcquireHandle(labs) + handle.Release() + } +} + +func BenchmarkFloat64CounterHandleAdd(b *testing.B) { + ctx := context.Background() + fix := newFixture(b) + labs := fix.sdk.Labels(makeLabels(1)...) + cnt := fix.sdk.NewFloat64Counter("float64.counter") + handle := cnt.AcquireHandle(labs) + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + handle.Add(ctx, 1.1) + } +} + +// Gauges + +func BenchmarkInt64GaugeAdd(b *testing.B) { + ctx := context.Background() + fix := newFixture(b) + labs := fix.sdk.Labels(makeLabels(1)...) + gau := fix.sdk.NewInt64Gauge("int64.gauge") + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + gau.Set(ctx, int64(i), labs) + } +} + +func BenchmarkInt64GaugeAcquireHandle(b *testing.B) { + fix := newFixture(b) + labs := fix.sdk.Labels(makeLabels(1)...) + gau := fix.sdk.NewInt64Gauge("int64.gauge") + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + handle := gau.AcquireHandle(labs) + handle.Release() + } +} + +func BenchmarkInt64GaugeHandleAdd(b *testing.B) { + ctx := context.Background() + fix := newFixture(b) + labs := fix.sdk.Labels(makeLabels(1)...) + gau := fix.sdk.NewInt64Gauge("int64.gauge") + handle := gau.AcquireHandle(labs) + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + handle.Set(ctx, int64(i)) + } +} + +func BenchmarkFloat64GaugeAdd(b *testing.B) { + ctx := context.Background() + fix := newFixture(b) + labs := fix.sdk.Labels(makeLabels(1)...) + gau := fix.sdk.NewFloat64Gauge("float64.gauge") + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + gau.Set(ctx, float64(i), labs) + } +} + +func BenchmarkFloat64GaugeAcquireHandle(b *testing.B) { + fix := newFixture(b) + labs := fix.sdk.Labels(makeLabels(1)...) + gau := fix.sdk.NewFloat64Gauge("float64.gauge") + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + handle := gau.AcquireHandle(labs) + handle.Release() + } +} + +func BenchmarkFloat64GaugeHandleAdd(b *testing.B) { + ctx := context.Background() + fix := newFixture(b) + labs := fix.sdk.Labels(makeLabels(1)...) + gau := fix.sdk.NewFloat64Gauge("float64.gauge") + handle := gau.AcquireHandle(labs) + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + handle.Set(ctx, float64(i)) + } +} + +// Measures + +func benchmarkInt64MeasureAdd(b *testing.B, name string) { + ctx := context.Background() + fix := newFixture(b) + labs := fix.sdk.Labels(makeLabels(1)...) + mea := fix.sdk.NewInt64Measure(name) + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + mea.Record(ctx, int64(i), labs) + } +} + +func benchmarkInt64MeasureAcquireHandle(b *testing.B, name string) { + fix := newFixture(b) + labs := fix.sdk.Labels(makeLabels(1)...) + mea := fix.sdk.NewInt64Measure(name) + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + handle := mea.AcquireHandle(labs) + handle.Release() + } +} + +func benchmarkInt64MeasureHandleAdd(b *testing.B, name string) { + ctx := context.Background() + fix := newFixture(b) + labs := fix.sdk.Labels(makeLabels(1)...) + mea := fix.sdk.NewInt64Measure(name) + handle := mea.AcquireHandle(labs) + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + handle.Record(ctx, int64(i)) + } +} + +func benchmarkFloat64MeasureAdd(b *testing.B, name string) { + ctx := context.Background() + fix := newFixture(b) + labs := fix.sdk.Labels(makeLabels(1)...) + mea := fix.sdk.NewFloat64Measure(name) + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + mea.Record(ctx, float64(i), labs) + } +} + +func benchmarkFloat64MeasureAcquireHandle(b *testing.B, name string) { + fix := newFixture(b) + labs := fix.sdk.Labels(makeLabels(1)...) + mea := fix.sdk.NewFloat64Measure(name) + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + handle := mea.AcquireHandle(labs) + handle.Release() + } +} + +func benchmarkFloat64MeasureHandleAdd(b *testing.B, name string) { + ctx := context.Background() + fix := newFixture(b) + labs := fix.sdk.Labels(makeLabels(1)...) + mea := fix.sdk.NewFloat64Measure(name) + handle := mea.AcquireHandle(labs) + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + handle.Record(ctx, float64(i)) + } +} + +// MaxSumCount + +func BenchmarkInt64MaxSumCountAdd(b *testing.B) { + benchmarkInt64MeasureAdd(b, "int64.maxsumcount") +} + +func BenchmarkInt64MaxSumCountAcquireHandle(b *testing.B) { + benchmarkInt64MeasureAcquireHandle(b, "int64.maxsumcount") +} + +func BenchmarkInt64MaxSumCountHandleAdd(b *testing.B) { + benchmarkInt64MeasureHandleAdd(b, "int64.maxsumcount") +} + +func BenchmarkFloat64MaxSumCountAdd(b *testing.B) { + benchmarkFloat64MeasureAdd(b, "float64.maxsumcount") +} + +func BenchmarkFloat64MaxSumCountAcquireHandle(b *testing.B) { + benchmarkFloat64MeasureAcquireHandle(b, "float64.maxsumcount") +} + +func BenchmarkFloat64MaxSumCountHandleAdd(b *testing.B) { + benchmarkFloat64MeasureHandleAdd(b, "float64.maxsumcount") +} + +// DDSketch + +func BenchmarkInt64DDSketchAdd(b *testing.B) { + benchmarkInt64MeasureAdd(b, "int64.ddsketch") +} + +func BenchmarkInt64DDSketchAcquireHandle(b *testing.B) { + benchmarkInt64MeasureAcquireHandle(b, "int64.ddsketch") +} + +func BenchmarkInt64DDSketchHandleAdd(b *testing.B) { + benchmarkInt64MeasureHandleAdd(b, "int64.ddsketch") +} + +func BenchmarkFloat64DDSketchAdd(b *testing.B) { + benchmarkFloat64MeasureAdd(b, "float64.ddsketch") +} + +func BenchmarkFloat64DDSketchAcquireHandle(b *testing.B) { + benchmarkFloat64MeasureAcquireHandle(b, "float64.ddsketch") +} + +func BenchmarkFloat64DDSketchHandleAdd(b *testing.B) { + benchmarkFloat64MeasureHandleAdd(b, "float64.ddsketch") +} diff --git a/sdk/metric/doc.go b/sdk/metric/doc.go new file mode 100644 index 00000000000..9957182eabd --- /dev/null +++ b/sdk/metric/doc.go @@ -0,0 +1,60 @@ +// 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 metric implements the OpenTelemetry `Meter` API. The SDK +supports configurable metrics export behavior through a +`export.MetricBatcher` API. Most metrics behavior is controlled +by the `MetricBatcher`, including: + +1. Selecting the concrete type of aggregation to use +2. Receiving exported data during SDK.Collect() + +The call to SDK.Collect() initiates collection. The SDK calls the +`MetricBatcher` for each current record, asking the aggregator to +export itself. Aggregators, found in `./aggregators`, are responsible +for receiving updates and exporting their current state. + +The SDK.Collect() API should be called by an exporter. During the +call to Collect(), the exporter receives calls in a single-threaded +context. No locking is required because the SDK.Collect() call +prevents concurrency. + +The SDK uses lock-free algorithms to maintain its internal state. +There are three central data structures at work: + +1. A sync.Map maps unique (InstrumentID, LabelSet) to records +2. A "primary" atomic list of records +3. A "reclaim" atomic list of records + +Collection is oriented around epochs. The SDK internally has a +notion of the "current" epoch, which is incremented each time +Collect() is called. Records contain two atomic counter values, +the epoch in which it was last modified and the epoch in which it +was last collected. Records may be garbage collected when the +epoch in which they were last updated is less than the epoch in +which they were last collected. + +Collect() performs a record-by-record scan of all active records +and exports their current state, before incrementing the current +epoch. Collection events happen at a point in time during +`Collect()`, but all records are not collected in the same instant. + +The purpose of the two lists: the primary list is appended-to when +new handles are created and atomically cleared during collect. The +reclaim list is used as a second chance, in case there is a race +between looking up a record and record deletion. +*/ +package metric diff --git a/sdk/metric/list.go b/sdk/metric/list.go new file mode 100644 index 00000000000..ddff72c96c3 --- /dev/null +++ b/sdk/metric/list.go @@ -0,0 +1,80 @@ +// 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 metric + +import ( + "sync/atomic" + "unsafe" +) + +func (l *sortedLabels) Len() int { + return len(*l) +} + +func (l *sortedLabels) Swap(i, j int) { + (*l)[i], (*l)[j] = (*l)[j], (*l)[i] +} + +func (l *sortedLabels) Less(i, j int) bool { + return (*l)[i].Key < (*l)[j].Key +} + +func (m *SDK) addPrimary(rec *record) { + for { + rec.next.primary.store(m.records.primary.load()) + if atomic.CompareAndSwapPointer( + &m.records.primary.ptr, + rec.next.primary.ptr, + unsafe.Pointer(rec), + ) { + return + } + } +} + +func (m *SDK) addReclaim(rec *record) { + for { + rec.next.reclaim.store(m.records.reclaim.load()) + if atomic.CompareAndSwapPointer( + &m.records.reclaim.ptr, + rec.next.reclaim.ptr, + unsafe.Pointer(rec), + ) { + return + } + } +} + +func (s *singlePtr) swapNil() *record { + for { + newValue := unsafe.Pointer(nil) + swapped := atomic.LoadPointer(&s.ptr) + if atomic.CompareAndSwapPointer(&s.ptr, swapped, newValue) { + return (*record)(swapped) + } + } +} + +func (s *singlePtr) load() *record { + return (*record)(atomic.LoadPointer(&s.ptr)) +} + +func (s *singlePtr) store(r *record) { + atomic.StorePointer(&s.ptr, unsafe.Pointer(r)) +} + +func (s *singlePtr) clear() { + atomic.StorePointer(&s.ptr, unsafe.Pointer(nil)) +} diff --git a/sdk/metric/monotone_test.go b/sdk/metric/monotone_test.go new file mode 100644 index 00000000000..b4cdfd79554 --- /dev/null +++ b/sdk/metric/monotone_test.go @@ -0,0 +1,124 @@ +// 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 metric_test + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/api/core" + "go.opentelemetry.io/api/key" + "go.opentelemetry.io/api/metric" + "go.opentelemetry.io/sdk/export" + sdk "go.opentelemetry.io/sdk/metric" + "go.opentelemetry.io/sdk/metric/aggregator/gauge" +) + +type monotoneBatcher struct { + t *testing.T + + collections int + currentValue *core.Number + currentTime *time.Time +} + +func (m *monotoneBatcher) AggregatorFor(rec export.MetricRecord) export.MetricAggregator { + return gauge.New() +} + +func (m *monotoneBatcher) Export(_ context.Context, record export.MetricRecord, agg export.MetricAggregator) { + require.Equal(m.t, "my.gauge.name", record.Descriptor().Name()) + require.Equal(m.t, 1, len(record.Labels())) + require.Equal(m.t, "a", string(record.Labels()[0].Key)) + require.Equal(m.t, "b", record.Labels()[0].Value.Emit()) + + gauge := agg.(*gauge.Aggregator) + val := gauge.AsNumber() + ts := gauge.Timestamp() + + m.currentValue = &val + m.currentTime = &ts + m.collections++ +} + +func TestMonotoneGauge(t *testing.T) { + ctx := context.Background() + batcher := &monotoneBatcher{ + t: t, + } + sdk := sdk.New(batcher) + + gauge := sdk.NewInt64Gauge("my.gauge.name", metric.WithMonotonic(true)) + + handle := gauge.AcquireHandle(sdk.Labels(key.String("a", "b"))) + + require.Nil(t, batcher.currentTime) + require.Nil(t, batcher.currentValue) + + before := time.Now() + + handle.Set(ctx, 1) + + // Until collection, expect nil. + require.Nil(t, batcher.currentTime) + require.Nil(t, batcher.currentValue) + + sdk.Collect(ctx) + + require.NotNil(t, batcher.currentValue) + require.Equal(t, core.NewInt64Number(1), *batcher.currentValue) + require.True(t, before.Before(*batcher.currentTime)) + + before = *batcher.currentTime + + // Collect would ordinarily flush the record, except we're using a handle. + sdk.Collect(ctx) + + require.Equal(t, 2, batcher.collections) + + // Increase the value to 2. + handle.Set(ctx, 2) + + sdk.Collect(ctx) + + require.Equal(t, 3, batcher.collections) + require.Equal(t, core.NewInt64Number(2), *batcher.currentValue) + require.True(t, before.Before(*batcher.currentTime)) + + before = *batcher.currentTime + + sdk.Collect(ctx) + require.Equal(t, 4, batcher.collections) + + // Try to lower the value to 1, it will fail. + handle.Set(ctx, 1) + sdk.Collect(ctx) + + // The value and timestamp are both unmodified + require.Equal(t, 5, batcher.collections) + require.Equal(t, core.NewInt64Number(2), *batcher.currentValue) + require.Equal(t, before, *batcher.currentTime) + + // Update with the same value, update the timestamp. + handle.Set(ctx, 2) + sdk.Collect(ctx) + + require.Equal(t, 6, batcher.collections) + require.Equal(t, core.NewInt64Number(2), *batcher.currentValue) + require.True(t, before.Before(*batcher.currentTime)) +} diff --git a/sdk/metric/sdk.go b/sdk/metric/sdk.go new file mode 100644 index 00000000000..14e045c5cd2 --- /dev/null +++ b/sdk/metric/sdk.go @@ -0,0 +1,481 @@ +// 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 metric + +import ( + "bytes" + "context" + "sort" + "sync" + "sync/atomic" + "unsafe" + + "go.opentelemetry.io/api/core" + "go.opentelemetry.io/api/metric" + api "go.opentelemetry.io/api/metric" + "go.opentelemetry.io/sdk/export" +) + +type ( + // SDK implements the OpenTelemetry Meter API. The SDK is + // bound to a single export.MetricBatcher in `New()`. + // + // The SDK supports a Collect() API to gather and export + // current data. Collect() should be arranged according to + // the exporter model. Push-based exporters will setup a + // timer to call Collect() periodically. Pull-based exporters + // will call Collect() when a pull request arrives. + SDK struct { + // current maps `mapkey` to *record. + current sync.Map + + // pool is a pool of labelset builders. + pool sync.Pool // *bytes.Buffer + + // empty is the (singleton) result of Labels() + // w/ zero arguments. + empty labels + + // records is the head of both the primary and the + // reclaim records lists. + records doublePtr + + // currentEpoch is the current epoch number. It is + // incremented in `Collect()`. + currentEpoch int64 + + // exporter is the configured exporter+configuration. + exporter export.MetricBatcher + + // collectLock prevents simultaneous calls to Collect(). + collectLock sync.Mutex + } + + instrument struct { + descriptor *export.Descriptor + meter *SDK + } + + // sortedLabels are used to de-duplicate and canonicalize labels. + sortedLabels []core.KeyValue + + // labels implements the OpenTelemetry LabelSet API, + // represents an internalized set of labels that may be used + // repeatedly. + labels struct { + meter *SDK + sorted []core.KeyValue + encoded string + } + + // mapkey uniquely describes a metric instrument in terms of + // its InstrumentID and the encoded form of its LabelSet. + mapkey struct { + descriptor *export.Descriptor + encoded string + } + + // record maintains the state of one metric instrument. Due + // the use of lock-free algorithms, there may be more than one + // `record` in existence at a time, although at most one can + // be referenced from the `SDK.current` map. + record struct { + // labels is the LabelSet passed by the user. + labels *labels + + // descriptor describes the metric instrument. + descriptor *export.Descriptor + + // refcount counts the number of active handles on + // referring to this record. active handles prevent + // removing the record from the current map. + refcount int64 + + // collectedEpoch is the epoch number for which this + // record has been exported. This is modified by the + // `Collect()` method. + collectedEpoch int64 + + // modifiedEpoch is the latest epoch number for which + // this record was updated. Generally, if + // modifiedEpoch is less than collectedEpoch, this + // record is due for reclaimation. + modifiedEpoch int64 + + // reclaim is an atomic to control the start of reclaiming. + reclaim int64 + + // recorder implements the actual RecordOne() API, + // depending on the type of aggregation. If nil, the + // metric was disabled by the exporter. + recorder export.MetricAggregator + + // next contains the next pointer for both the primary + // and the reclaim lists. + next doublePtr + } + + // singlePointer wraps an unsafe.Pointer and supports basic + // load(), store(), clear(), and swapNil() operations. + singlePtr struct { + ptr unsafe.Pointer + } + + // doublePtr is used for the head and next links of two lists. + doublePtr struct { + primary singlePtr + reclaim singlePtr + } +) + +var ( + _ api.Meter = &SDK{} + _ api.LabelSet = &labels{} + _ api.InstrumentImpl = &instrument{} + _ api.HandleImpl = &record{} + _ export.MetricRecord = &record{} + + // hazardRecord is used as a pointer value that indicates the + // value is not included in any list. (`nil` would be + // ambiguous, since the final element in a list has `nil` as + // the next pointer). + hazardRecord = &record{} +) + +func (i *instrument) Meter() api.Meter { + return i.meter +} + +func (i *instrument) acquireHandle(ls *labels) *record { + // Create lookup key for sync.Map + mk := mapkey{ + descriptor: i.descriptor, + encoded: ls.encoded, + } + + // There's a memory allocation here. + rec := &record{ + labels: ls, + descriptor: i.descriptor, + refcount: 1, + collectedEpoch: -1, + modifiedEpoch: 0, + } + + // Load/Store: there's a memory allocation to place `mk` into + // an interface here. + if actual, loaded := i.meter.current.LoadOrStore(mk, rec); loaded { + // Existing record case. + rec = actual.(*record) + atomic.AddInt64(&rec.refcount, 1) + return rec + } + rec.recorder = i.meter.exporter.AggregatorFor(rec) + + i.meter.addPrimary(rec) + return rec +} + +func (i *instrument) AcquireHandle(ls api.LabelSet) api.HandleImpl { + labs := i.meter.labsFor(ls) + return i.acquireHandle(labs) +} + +func (i *instrument) RecordOne(ctx context.Context, number core.Number, ls api.LabelSet) { + ourLs := i.meter.labsFor(ls) + h := i.acquireHandle(ourLs) + defer h.Release() + h.RecordOne(ctx, number) +} + +// New constructs a new SDK for the given exporter. This SDK supports +// only a single exporter. +// +// The SDK does not start any background process to collect itself +// periodically, this responsbility lies with the exporter, typically, +// depending on the type of export. For example, a pull-based +// exporter will call Collect() when it receives a request to scrape +// current metric values. A push-based exporter should configure its +// own periodic collection. +func New(exporter export.MetricBatcher) *SDK { + m := &SDK{ + pool: sync.Pool{ + New: func() interface{} { + return &bytes.Buffer{} + }, + }, + exporter: exporter, + } + m.empty.meter = m + return m +} + +// Labels returns a LabelSet corresponding to the arguments. Passed +// labels are de-duplicated, with last-value-wins semantics. +func (m *SDK) Labels(kvs ...core.KeyValue) api.LabelSet { + // Note: This computes a canonical encoding of the labels to + // use as a map key. It happens to use the encoding used by + // statsd for labels, allowing an optimization for statsd + // exporters. This could be made configurable in the + // constructor, to support the same optimization for different + // exporters. + + // Check for empty set. + if len(kvs) == 0 { + return &m.empty + } + + // Sort and de-duplicate. + sorted := sortedLabels(kvs) + sort.Stable(&sorted) + oi := 1 + for i := 1; i < len(sorted); i++ { + if sorted[i-1].Key == sorted[i].Key { + sorted[oi-1] = sorted[i] + continue + } + sorted[oi] = sorted[i] + oi++ + } + sorted = sorted[0:oi] + + // Serialize. + buf := m.pool.Get().(*bytes.Buffer) + defer m.pool.Put(buf) + buf.Reset() + _, _ = buf.WriteRune('|') + delimiter := '#' + for _, kv := range sorted { + _, _ = buf.WriteRune(delimiter) + _, _ = buf.WriteString(string(kv.Key)) + _, _ = buf.WriteRune(':') + _, _ = buf.WriteString(kv.Value.Emit()) + delimiter = ',' + } + + return &labels{ + meter: m, + sorted: sorted, + encoded: buf.String(), + } +} + +// labsFor sanitizes the input LabelSet. The input will be rejected +// if it was created by another Meter instance, for example. +func (m *SDK) labsFor(ls api.LabelSet) *labels { + if l, _ := ls.(*labels); l != nil && l.meter == m { + return l + } + return &m.empty +} + +func (m *SDK) newInstrument(name string, metricKind export.MetricKind, numberKind core.NumberKind, opts *api.Options) *instrument { + descriptor := export.NewDescriptor( + name, + metricKind, + opts.Keys, + opts.Description, + opts.Unit, + numberKind, + opts.Alternate) + return &instrument{ + descriptor: descriptor, + meter: m, + } +} + +func (m *SDK) newCounterInstrument(name string, numberKind core.NumberKind, cos ...api.CounterOptionApplier) *instrument { + opts := api.Options{} + api.ApplyCounterOptions(&opts, cos...) + return m.newInstrument(name, export.CounterMetricKind, numberKind, &opts) +} + +func (m *SDK) newGaugeInstrument(name string, numberKind core.NumberKind, gos ...api.GaugeOptionApplier) *instrument { + opts := api.Options{} + api.ApplyGaugeOptions(&opts, gos...) + return m.newInstrument(name, export.GaugeMetricKind, numberKind, &opts) +} + +func (m *SDK) newMeasureInstrument(name string, numberKind core.NumberKind, mos ...api.MeasureOptionApplier) *instrument { + opts := api.Options{} + api.ApplyMeasureOptions(&opts, mos...) + return m.newInstrument(name, export.MeasureMetricKind, numberKind, &opts) +} + +func (m *SDK) NewInt64Counter(name string, cos ...api.CounterOptionApplier) api.Int64Counter { + return api.WrapInt64CounterInstrument(m.newCounterInstrument(name, core.Int64NumberKind, cos...)) +} + +func (m *SDK) NewFloat64Counter(name string, cos ...api.CounterOptionApplier) api.Float64Counter { + return api.WrapFloat64CounterInstrument(m.newCounterInstrument(name, core.Float64NumberKind, cos...)) +} + +func (m *SDK) NewInt64Gauge(name string, gos ...api.GaugeOptionApplier) api.Int64Gauge { + return api.WrapInt64GaugeInstrument(m.newGaugeInstrument(name, core.Int64NumberKind, gos...)) +} + +func (m *SDK) NewFloat64Gauge(name string, gos ...api.GaugeOptionApplier) api.Float64Gauge { + return api.WrapFloat64GaugeInstrument(m.newGaugeInstrument(name, core.Float64NumberKind, gos...)) +} + +func (m *SDK) NewInt64Measure(name string, mos ...api.MeasureOptionApplier) api.Int64Measure { + return api.WrapInt64MeasureInstrument(m.newMeasureInstrument(name, core.Int64NumberKind, mos...)) +} + +func (m *SDK) NewFloat64Measure(name string, mos ...api.MeasureOptionApplier) api.Float64Measure { + return api.WrapFloat64MeasureInstrument(m.newMeasureInstrument(name, core.Float64NumberKind, mos...)) +} + +// saveFromReclaim puts a record onto the "reclaim" list when it +// detects an attempt to delete the record while it is still in use. +func (m *SDK) saveFromReclaim(rec *record) { + for { + + reclaimed := atomic.LoadInt64(&rec.reclaim) + if reclaimed != 0 { + return + } + if atomic.CompareAndSwapInt64(&rec.reclaim, 0, 1) { + break + } + } + + m.addReclaim(rec) +} + +// Collect traverses the list of active records and exports data for +// each active instrument. Collect() may not be called concurrently. +// +// During the collection pass, the export.MetricBatcher will receive +// one Export() call per current aggregation. +func (m *SDK) Collect(ctx context.Context) { + m.collectLock.Lock() + defer m.collectLock.Unlock() + + var next *record + for inuse := m.records.primary.swapNil(); inuse != nil; inuse = next { + next = inuse.next.primary.load() + + refcount := atomic.LoadInt64(&inuse.refcount) + + if refcount > 0 { + m.collect(ctx, inuse) + m.addPrimary(inuse) + continue + } + + modified := atomic.LoadInt64(&inuse.modifiedEpoch) + collected := atomic.LoadInt64(&inuse.collectedEpoch) + m.collect(ctx, inuse) + + if modified >= collected { + atomic.StoreInt64(&inuse.collectedEpoch, m.currentEpoch) + m.addPrimary(inuse) + continue + } + + // Remove this entry. + m.current.Delete(inuse.mapkey()) + inuse.next.primary.store(hazardRecord) + } + + for chances := m.records.reclaim.swapNil(); chances != nil; chances = next { + atomic.StoreInt64(&chances.collectedEpoch, m.currentEpoch) + + next = chances.next.reclaim.load() + chances.next.reclaim.clear() + atomic.StoreInt64(&chances.reclaim, 0) + + if chances.next.primary.load() == hazardRecord { + m.collect(ctx, chances) + m.addPrimary(chances) + } + } + + m.currentEpoch++ +} + +func (m *SDK) collect(ctx context.Context, r *record) { + if r.recorder != nil { + r.recorder.Collect(ctx, r, m.exporter) + } +} + +// RecordBatch enters a batch of metric events. +func (m *SDK) RecordBatch(ctx context.Context, ls api.LabelSet, measurements ...api.Measurement) { + for _, meas := range measurements { + meas.InstrumentImpl().RecordOne(ctx, meas.Number(), ls) + } +} + +// GetDescriptor returns the descriptor of an instrument, which is not +// part of the public metric API. +func (m *SDK) GetDescriptor(inst metric.InstrumentImpl) *export.Descriptor { + if ii, ok := inst.(*instrument); ok { + return ii.descriptor + } + return nil +} + +func (l *labels) Meter() api.Meter { + return l.meter +} + +func (r *record) RecordOne(ctx context.Context, number core.Number) { + if r.recorder != nil { + r.recorder.Update(ctx, number, r) + } +} + +func (r *record) Release() { + for { + collected := atomic.LoadInt64(&r.collectedEpoch) + modified := atomic.LoadInt64(&r.modifiedEpoch) + + updated := collected + 1 + + if modified == updated { + // No change + break + } + if !atomic.CompareAndSwapInt64(&r.modifiedEpoch, modified, updated) { + continue + } + + if modified < collected { + // This record could have been reclaimed. + r.labels.meter.saveFromReclaim(r) + } + + break + } + + _ = atomic.AddInt64(&r.refcount, -1) +} + +func (r *record) mapkey() mapkey { + return mapkey{ + descriptor: r.descriptor, + encoded: r.labels.encoded, + } +} + +func (r *record) Descriptor() *export.Descriptor { + return r.descriptor +} + +func (r *record) Labels() []core.KeyValue { + return r.labels.sorted +} diff --git a/sdk/metric/stress_test.go b/sdk/metric/stress_test.go new file mode 100644 index 00000000000..d9c6b0a865e --- /dev/null +++ b/sdk/metric/stress_test.go @@ -0,0 +1,513 @@ +// 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. + +// This test is too large for the race detector. This SDK uses no locks +// that the race detector would help with, anyway. +// +build !race + +package metric_test + +import ( + "context" + "fmt" + "math" + "math/rand" + "runtime" + "sort" + "strings" + "sync" + "sync/atomic" + "testing" + "time" + + "go.opentelemetry.io/api/core" + "go.opentelemetry.io/api/key" + "go.opentelemetry.io/api/metric" + api "go.opentelemetry.io/api/metric" + "go.opentelemetry.io/sdk/export" + sdk "go.opentelemetry.io/sdk/metric" + "go.opentelemetry.io/sdk/metric/aggregator/counter" + "go.opentelemetry.io/sdk/metric/aggregator/gauge" +) + +const ( + concurrencyPerCPU = 100 + reclaimPeriod = time.Millisecond * 100 + testRun = time.Second + epsilon = 1e-10 +) + +type ( + testFixture struct { + stop int64 + expected sync.Map + received sync.Map // Note: doesn't require synchronization + wg sync.WaitGroup + impl testImpl + T *testing.T + + lock sync.Mutex + lused map[string]bool + + dupCheck map[testKey]int + totalDups int64 + } + + testKey struct { + labels string + descriptor *export.Descriptor + } + + testImpl struct { + newInstrument func(meter api.Meter, name string) withImpl + getUpdateValue func() core.Number + operate func(interface{}, context.Context, core.Number, api.LabelSet) + newStore func() interface{} + + // storeCollect and storeExpect are the same for + // counters, different for gauges, to ensure we are + // testing the timestamps correctly. + storeCollect func(store interface{}, value core.Number, ts time.Time) + storeExpect func(store interface{}, value core.Number) + readStore func(store interface{}) core.Number + equalValues func(a, b core.Number) bool + } + + withImpl interface { + Impl() metric.InstrumentImpl + } + + // gaugeState supports merging gauge values, for the case + // where a race condition causes duplicate records. We always + // take the later timestamp. + gaugeState struct { + raw core.Number + ts time.Time + } +) + +func concurrency() int { + return concurrencyPerCPU * runtime.NumCPU() +} + +func canonicalizeLabels(ls []core.KeyValue) string { + copy := append(ls[0:0:0], ls...) + sort.SliceStable(copy, func(i, j int) bool { + return copy[i].Key < copy[j].Key + }) + var b strings.Builder + for _, kv := range copy { + b.WriteString(string(kv.Key)) + b.WriteString("=") + b.WriteString(kv.Value.Emit()) + b.WriteString("$") + } + return b.String() +} + +func getPeriod() time.Duration { + dur := math.Max( + float64(reclaimPeriod)/10, + float64(reclaimPeriod)*(1+0.1*rand.NormFloat64()), + ) + return time.Duration(dur) +} + +func (f *testFixture) someLabels() []core.KeyValue { + n := 1 + rand.Intn(3) + l := make([]core.KeyValue, n) + + for { + oused := map[string]bool{} + for i := 0; i < n; i++ { + var k string + for { + k = fmt.Sprint("k", rand.Intn(1000000000)) + if !oused[k] { + oused[k] = true + break + } + } + l[i] = key.New(k).String(fmt.Sprint("v", rand.Intn(1000000000))) + } + lc := canonicalizeLabels(l) + f.lock.Lock() + avail := !f.lused[lc] + if avail { + f.lused[lc] = true + f.lock.Unlock() + return l + } + f.lock.Unlock() + } +} + +func (f *testFixture) startWorker(sdk *sdk.SDK, wg *sync.WaitGroup, i int) { + ctx := context.Background() + name := fmt.Sprint("test_", i) + instrument := f.impl.newInstrument(sdk, name) + descriptor := sdk.GetDescriptor(instrument.Impl()) + kvs := f.someLabels() + clabs := canonicalizeLabels(kvs) + labs := sdk.Labels(kvs...) + dur := getPeriod() + key := testKey{ + labels: clabs, + descriptor: descriptor, + } + for { + sleep := time.Duration(rand.ExpFloat64() * float64(dur)) + time.Sleep(sleep) + value := f.impl.getUpdateValue() + f.impl.operate(instrument, ctx, value, labs) + + actual, _ := f.expected.LoadOrStore(key, f.impl.newStore()) + + f.impl.storeExpect(actual, value) + + if atomic.LoadInt64(&f.stop) != 0 { + wg.Done() + return + } + } +} + +func (f *testFixture) assertTest(numCollect int) { + csize := 0 + f.received.Range(func(key, gstore interface{}) bool { + csize++ + gvalue := f.impl.readStore(gstore) + + estore, loaded := f.expected.Load(key) + if !loaded { + f.T.Error("Could not locate expected key: ", key) + } + evalue := f.impl.readStore(estore) + + if !f.impl.equalValues(evalue, gvalue) { + f.T.Error("Expected value mismatch: ", + evalue, "!=", gvalue, " for ", key) + } + return true + }) + rsize := 0 + f.expected.Range(func(key, value interface{}) bool { + rsize++ + if _, loaded := f.received.Load(key); !loaded { + f.T.Error("Did not receive expected key: ", key) + } + return true + }) + if rsize != csize { + f.T.Error("Did not receive the correct set of metrics: Received != Expected", rsize, csize) + } + + // Note: It's useful to know the test triggers this condition, + // but we can't assert it. Infrequently no duplicates are + // found, and we can't really force a race to happen. + // + // fmt.Printf("Test duplicate records seen: %.1f%%\n", + // float64(100*f.totalDups/int64(numCollect*concurrency()))) +} + +func (f *testFixture) preCollect() { + // Collect calls Export in a single-threaded context. No need + // to lock this struct. + f.dupCheck = map[testKey]int{} +} + +func (f *testFixture) AggregatorFor(record export.MetricRecord) export.MetricAggregator { + switch record.Descriptor().MetricKind() { + case export.CounterMetricKind: + return counter.New() + case export.GaugeMetricKind: + return gauge.New() + default: + panic("Not implemented for this test") + } +} + +func (f *testFixture) Export(ctx context.Context, record export.MetricRecord, agg export.MetricAggregator) { + desc := record.Descriptor() + key := testKey{ + labels: canonicalizeLabels(record.Labels()), + descriptor: desc, + } + if f.dupCheck[key] == 0 { + f.dupCheck[key]++ + } else { + f.totalDups++ + } + + actual, _ := f.received.LoadOrStore(key, f.impl.newStore()) + + switch desc.MetricKind() { + case export.CounterMetricKind: + f.impl.storeCollect(actual, agg.(*counter.Aggregator).AsNumber(), time.Time{}) + case export.GaugeMetricKind: + gauge := agg.(*gauge.Aggregator) + f.impl.storeCollect(actual, gauge.AsNumber(), gauge.Timestamp()) + default: + panic("Not used in this test") + } +} + +func stressTest(t *testing.T, impl testImpl) { + ctx := context.Background() + t.Parallel() + fixture := &testFixture{ + T: t, + impl: impl, + lused: map[string]bool{}, + } + cc := concurrency() + sdk := sdk.New(fixture) + fixture.wg.Add(cc + 1) + + for i := 0; i < cc; i++ { + go fixture.startWorker(sdk, &fixture.wg, i) + } + + numCollect := 0 + + go func() { + for { + time.Sleep(reclaimPeriod) + fixture.preCollect() + sdk.Collect(ctx) + numCollect++ + if atomic.LoadInt64(&fixture.stop) != 0 { + fixture.wg.Done() + return + } + } + }() + + time.Sleep(testRun) + atomic.StoreInt64(&fixture.stop, 1) + fixture.wg.Wait() + fixture.preCollect() + sdk.Collect(ctx) + numCollect++ + + fixture.assertTest(numCollect) +} + +func int64sEqual(a, b core.Number) bool { + return a.AsInt64() == b.AsInt64() +} + +func float64sEqual(a, b core.Number) bool { + diff := math.Abs(a.AsFloat64() - b.AsFloat64()) + return diff < math.Abs(a.AsFloat64())*epsilon +} + +// Counters + +func intCounterTestImpl(nonMonotonic bool) testImpl { + return testImpl{ + newInstrument: func(meter api.Meter, name string) withImpl { + return meter.NewInt64Counter(name, api.WithMonotonic(!nonMonotonic)) + }, + getUpdateValue: func() core.Number { + var offset int64 + if nonMonotonic { + offset = -50 + } + for { + x := offset + int64(rand.Intn(100)) + if x != 0 { + return core.NewInt64Number(x) + } + } + }, + operate: func(inst interface{}, ctx context.Context, value core.Number, labels api.LabelSet) { + counter := inst.(api.Int64Counter) + counter.Add(ctx, value.AsInt64(), labels) + }, + newStore: func() interface{} { + n := core.NewInt64Number(0) + return &n + }, + storeCollect: func(store interface{}, value core.Number, _ time.Time) { + store.(*core.Number).AddInt64Atomic(value.AsInt64()) + }, + storeExpect: func(store interface{}, value core.Number) { + store.(*core.Number).AddInt64Atomic(value.AsInt64()) + }, + readStore: func(store interface{}) core.Number { + return store.(*core.Number).AsNumberAtomic() + }, + equalValues: int64sEqual, + } +} + +func TestStressInt64CounterNormal(t *testing.T) { + stressTest(t, intCounterTestImpl(false)) +} + +func TestStressInt64CounterNonMonotonic(t *testing.T) { + stressTest(t, intCounterTestImpl(true)) +} + +func floatCounterTestImpl(nonMonotonic bool) testImpl { + return testImpl{ + newInstrument: func(meter api.Meter, name string) withImpl { + return meter.NewFloat64Counter(name, api.WithMonotonic(!nonMonotonic)) + }, + getUpdateValue: func() core.Number { + var offset float64 + if nonMonotonic { + offset = -0.5 + } + for { + x := offset + rand.Float64() + if x != 0 { + return core.NewFloat64Number(x) + } + } + }, + operate: func(inst interface{}, ctx context.Context, value core.Number, labels api.LabelSet) { + counter := inst.(api.Float64Counter) + counter.Add(ctx, value.AsFloat64(), labels) + }, + newStore: func() interface{} { + n := core.NewFloat64Number(0.0) + return &n + }, + storeCollect: func(store interface{}, value core.Number, _ time.Time) { + store.(*core.Number).AddFloat64Atomic(value.AsFloat64()) + }, + storeExpect: func(store interface{}, value core.Number) { + store.(*core.Number).AddFloat64Atomic(value.AsFloat64()) + }, + readStore: func(store interface{}) core.Number { + return store.(*core.Number).AsNumberAtomic() + }, + equalValues: float64sEqual, + } +} + +func TestStressFloat64CounterNormal(t *testing.T) { + stressTest(t, floatCounterTestImpl(false)) +} + +func TestStressFloat64CounterNonMonotonic(t *testing.T) { + stressTest(t, floatCounterTestImpl(true)) +} + +// Gauges + +func intGaugeTestImpl(monotonic bool) testImpl { + // (Now()-startTime) is used as a free monotonic source + startTime := time.Now() + + return testImpl{ + newInstrument: func(meter api.Meter, name string) withImpl { + return meter.NewInt64Gauge(name, api.WithMonotonic(monotonic)) + }, + getUpdateValue: func() core.Number { + if !monotonic { + r1 := rand.Int63() + return core.NewInt64Number(rand.Int63() - r1) + } + return core.NewInt64Number(int64(time.Since(startTime))) + }, + operate: func(inst interface{}, ctx context.Context, value core.Number, labels api.LabelSet) { + gauge := inst.(api.Int64Gauge) + gauge.Set(ctx, value.AsInt64(), labels) + }, + newStore: func() interface{} { + return &gaugeState{ + raw: core.NewInt64Number(0), + } + }, + storeCollect: func(store interface{}, value core.Number, ts time.Time) { + gs := store.(*gaugeState) + + if !ts.Before(gs.ts) { + gs.ts = ts + gs.raw.SetInt64Atomic(value.AsInt64()) + } + }, + storeExpect: func(store interface{}, value core.Number) { + gs := store.(*gaugeState) + gs.raw.SetInt64Atomic(value.AsInt64()) + }, + readStore: func(store interface{}) core.Number { + gs := store.(*gaugeState) + return gs.raw.AsNumberAtomic() + }, + equalValues: int64sEqual, + } +} + +func TestStressInt64GaugeNormal(t *testing.T) { + stressTest(t, intGaugeTestImpl(false)) +} + +func TestStressInt64GaugeMonotonic(t *testing.T) { + stressTest(t, intGaugeTestImpl(true)) +} + +func floatGaugeTestImpl(monotonic bool) testImpl { + // (Now()-startTime) is used as a free monotonic source + startTime := time.Now() + + return testImpl{ + newInstrument: func(meter api.Meter, name string) withImpl { + return meter.NewFloat64Gauge(name, api.WithMonotonic(monotonic)) + }, + getUpdateValue: func() core.Number { + if !monotonic { + return core.NewFloat64Number((-0.5 + rand.Float64()) * 100000) + } + return core.NewFloat64Number(float64(time.Since(startTime))) + }, + operate: func(inst interface{}, ctx context.Context, value core.Number, labels api.LabelSet) { + gauge := inst.(api.Float64Gauge) + gauge.Set(ctx, value.AsFloat64(), labels) + }, + newStore: func() interface{} { + return &gaugeState{ + raw: core.NewFloat64Number(0), + } + }, + storeCollect: func(store interface{}, value core.Number, ts time.Time) { + gs := store.(*gaugeState) + + if !ts.Before(gs.ts) { + gs.ts = ts + gs.raw.SetFloat64Atomic(value.AsFloat64()) + } + }, + storeExpect: func(store interface{}, value core.Number) { + gs := store.(*gaugeState) + gs.raw.SetFloat64Atomic(value.AsFloat64()) + }, + readStore: func(store interface{}) core.Number { + gs := store.(*gaugeState) + return gs.raw.AsNumberAtomic() + }, + equalValues: float64sEqual, + } +} + +func TestStressFloat64GaugeNormal(t *testing.T) { + stressTest(t, floatGaugeTestImpl(false)) +} + +func TestStressFloat64GaugeMonotonic(t *testing.T) { + stressTest(t, floatGaugeTestImpl(true)) +}