1
0
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:
Joshua MacDonald 2019-10-29 13:27:22 -07:00 committed by GitHub
parent 7d301220a2
commit 937f4ff8b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 3530 additions and 508 deletions

View File

@ -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
View 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
View 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)
}
}
}
}

View 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]]
}

View File

@ -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.

View File

@ -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)
}

View File

@ -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,
}
}

View File

@ -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))
}

View File

@ -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))
}

View File

@ -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))
}

View File

@ -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...)

View File

@ -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{}
}

View File

@ -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)}
}

View File

@ -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)
}

View File

@ -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)
}
}
}
}

View File

@ -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]]
}

View File

@ -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=

View File

@ -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()

View File

@ -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=

View File

@ -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=

View File

@ -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=

View File

@ -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=

View File

@ -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=

View File

@ -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
View File

@ -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
View File

@ -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=

View File

@ -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
View 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
}

View 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)
}

View 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")
})
}

View 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))
}

View 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")
})
}

View 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
}
}
}

View 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")
})
}

View 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
}
}
}

View 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")
})
}

View 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))
}

View 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
View 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
View 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
View 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
View 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
View 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))
}