You've already forked opentelemetry-go
							
							
				mirror of
				https://github.com/open-telemetry/opentelemetry-go.git
				synced 2025-10-31 00:07:40 +02:00 
			
		
		
		
	Metrics SDK work-in-progress (#172)
Introduce the new SDK, four aggregators, and an export interface.
This commit is contained in:
		
							
								
								
									
										4
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								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: | ||||
|   | ||||
							
								
								
									
										546
									
								
								api/core/number.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										546
									
								
								api/core/number.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										148
									
								
								api/core/number_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								api/core/number_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										25
									
								
								api/core/numberkind_string.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								api/core/numberkind_string.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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]] | ||||
| } | ||||
| @@ -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. | ||||
|   | ||||
| @@ -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) | ||||
| } | ||||
|   | ||||
| @@ -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, value MeasurementValue, labels LabelSet) { | ||||
| 	m.instrument.RecordOne(ctx, value, labels) | ||||
| func (m commonMetric) recordOne(ctx context.Context, number core.Number, labels LabelSet) { | ||||
| 	m.instrument.RecordOne(ctx, number, labels) | ||||
| } | ||||
|  | ||||
| func (h commonHandle) recordOne(ctx context.Context, value MeasurementValue) { | ||||
| 	h.handle.RecordOne(ctx, value) | ||||
| func (m commonMetric) Impl() InstrumentImpl { | ||||
| 	return m.instrument | ||||
| } | ||||
|  | ||||
| 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, | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -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)) | ||||
| } | ||||
|   | ||||
| @@ -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)) | ||||
| } | ||||
|   | ||||
| @@ -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)) | ||||
| } | ||||
|   | ||||
| @@ -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...) | ||||
|   | ||||
| @@ -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{} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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)} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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) | ||||
| } | ||||
| @@ -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) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -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]] | ||||
| } | ||||
| @@ -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= | ||||
|   | ||||
| @@ -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() | ||||
|   | ||||
| @@ -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= | ||||
|   | ||||
| @@ -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= | ||||
|   | ||||
| @@ -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= | ||||
|   | ||||
| @@ -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= | ||||
|   | ||||
| @@ -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= | ||||
|   | ||||
| @@ -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= | ||||
|   | ||||
							
								
								
									
										5
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								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 | ||||
|   | ||||
							
								
								
									
										8
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								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= | ||||
|   | ||||
| @@ -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) | ||||
| } | ||||
|   | ||||
							
								
								
									
										117
									
								
								sdk/export/metric.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								sdk/export/metric.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										66
									
								
								sdk/metric/aggregator/counter/counter.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								sdk/metric/aggregator/counter/counter.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
| } | ||||
							
								
								
									
										93
									
								
								sdk/metric/aggregator/counter/counter_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								sdk/metric/aggregator/counter/counter_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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") | ||||
| 	}) | ||||
| } | ||||
							
								
								
									
										103
									
								
								sdk/metric/aggregator/ddsketch/ddsketch.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								sdk/metric/aggregator/ddsketch/ddsketch.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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)) | ||||
| } | ||||
							
								
								
									
										67
									
								
								sdk/metric/aggregator/ddsketch/ddsketch_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								sdk/metric/aggregator/ddsketch/ddsketch_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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") | ||||
| 	}) | ||||
| } | ||||
							
								
								
									
										127
									
								
								sdk/metric/aggregator/gauge/gauge.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								sdk/metric/aggregator/gauge/gauge.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										96
									
								
								sdk/metric/aggregator/gauge/gauge_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								sdk/metric/aggregator/gauge/gauge_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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") | ||||
| 	}) | ||||
| } | ||||
							
								
								
									
										103
									
								
								sdk/metric/aggregator/maxsumcount/msc.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								sdk/metric/aggregator/maxsumcount/msc.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										59
									
								
								sdk/metric/aggregator/maxsumcount/msc_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								sdk/metric/aggregator/maxsumcount/msc_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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") | ||||
| 	}) | ||||
| } | ||||
							
								
								
									
										103
									
								
								sdk/metric/aggregator/test/test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								sdk/metric/aggregator/test/test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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)) | ||||
| } | ||||
							
								
								
									
										412
									
								
								sdk/metric/benchmark_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										412
									
								
								sdk/metric/benchmark_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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") | ||||
| } | ||||
							
								
								
									
										60
									
								
								sdk/metric/doc.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								sdk/metric/doc.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
							
								
								
									
										80
									
								
								sdk/metric/list.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								sdk/metric/list.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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)) | ||||
| } | ||||
							
								
								
									
										124
									
								
								sdk/metric/monotone_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								sdk/metric/monotone_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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)) | ||||
| } | ||||
							
								
								
									
										481
									
								
								sdk/metric/sdk.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										481
									
								
								sdk/metric/sdk.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										513
									
								
								sdk/metric/stress_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										513
									
								
								sdk/metric/stress_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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)) | ||||
| } | ||||
		Reference in New Issue
	
	Block a user