mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2025-04-09 07:03:54 +02:00
Metrics SDK work-in-progress (#172)
Introduce the new SDK, four aggregators, and an export interface.
This commit is contained in:
parent
7d301220a2
commit
937f4ff8b0
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))
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user