1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2025-09-16 09:26:25 +02:00

Some metrics followup (#177)

* prevent passing observer descriptors to NewHandle

* add some test for metrics

* reword some docs
This commit is contained in:
Krzesimir Nowak
2019-10-15 18:28:36 +02:00
committed by rghetia
parent e0eccc9a57
commit 388d3248fe
11 changed files with 1055 additions and 77 deletions

View File

@@ -66,6 +66,23 @@ type ObservationCallback func(LabelSet, MeasurementValue)
// the registered observers.
type ObserverCallback func(Meter, Observer, ObservationCallback)
// WithDescriptor is an interface that all metric implement.
type WithDescriptor interface {
// Descriptor returns a descriptor of this metric.
Descriptor() *Descriptor
}
type hiddenType struct{}
// ExplicitReportingMetric is an interface that is implemented only by
// metrics that support getting a Handle.
type ExplicitReportingMetric interface {
WithDescriptor
// SupportHandle is a dummy function that can be only
// implemented in this package.
SupportHandle() hiddenType
}
// Meter is an interface to the metrics portion of the OpenTelemetry SDK.
type Meter interface {
// DefineLabels returns a reference to a set of labels that
@@ -78,7 +95,7 @@ type Meter interface {
// NewHandle creates a Handle that contains the passed
// key-value pairs. This should not be used directly - prefer
// using GetHandle function of a metric.
NewHandle(*Descriptor, LabelSet) Handle
NewHandle(ExplicitReportingMetric, LabelSet) Handle
// DeleteHandle destroys the Handle and does a cleanup of the
// underlying resources.
DeleteHandle(Handle)

718
api/metric/api_test.go Normal file
View File

@@ -0,0 +1,718 @@
// 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 (
"context"
"fmt"
"testing"
"go.opentelemetry.io/api/core"
"go.opentelemetry.io/api/unit"
)
func TestCounterOptions(t *testing.T) {
type testcase struct {
name string
opts []CounterOptionApplier
keys []core.Key
desc string
unit unit.Unit
alt bool
}
testcases := []testcase{
{
name: "no opts",
opts: nil,
keys: nil,
desc: "",
unit: "",
alt: false,
},
{
name: "keys keys keys",
opts: []CounterOptionApplier{
WithKeys(key("foo"), key("foo2")),
WithKeys(key("bar"), key("bar2")),
WithKeys(key("baz"), key("baz2")),
},
keys: []core.Key{
key("foo"), key("foo2"),
key("bar"), key("bar2"),
key("baz"), key("baz2"),
},
desc: "",
unit: "",
alt: false,
},
{
name: "description",
opts: []CounterOptionApplier{
WithDescription("stuff"),
},
keys: nil,
desc: "stuff",
unit: "",
alt: false,
},
{
name: "description override",
opts: []CounterOptionApplier{
WithDescription("stuff"),
WithDescription("things"),
},
keys: nil,
desc: "things",
unit: "",
alt: false,
},
{
name: "unit",
opts: []CounterOptionApplier{
WithUnit("s"),
},
keys: nil,
desc: "",
unit: "s",
alt: false,
},
{
name: "unit override",
opts: []CounterOptionApplier{
WithUnit("s"),
WithUnit("h"),
},
keys: nil,
desc: "",
unit: "h",
alt: false,
},
{
name: "nonmonotonic",
opts: []CounterOptionApplier{
WithNonMonotonic(true),
},
keys: nil,
desc: "",
unit: "",
alt: true,
},
{
name: "nonmonotonic, but not really",
opts: []CounterOptionApplier{
WithNonMonotonic(true),
WithNonMonotonic(false),
},
keys: nil,
desc: "",
unit: "",
alt: false,
},
}
checkCounterDescriptor := func(tt testcase, vk ValueKind, d *Descriptor) {
e := descriptor{
name: tt.name,
keys: tt.keys,
desc: tt.desc,
unit: tt.unit,
alt: tt.alt,
kind: CounterKind,
vk: vk,
}
checkDescriptor(t, e, d)
}
for idx, tt := range testcases {
t.Logf("Testing counter case %s (%d)", tt.name, idx)
f := NewFloat64Counter(tt.name, tt.opts...)
checkCounterDescriptor(tt, Float64ValueKind, f.Descriptor())
i := NewInt64Counter(tt.name, tt.opts...)
checkCounterDescriptor(tt, Int64ValueKind, i.Descriptor())
}
}
func TestGaugeOptions(t *testing.T) {
type testcase struct {
name string
opts []GaugeOptionApplier
keys []core.Key
desc string
unit unit.Unit
alt bool
}
testcases := []testcase{
{
name: "no opts",
opts: nil,
keys: nil,
desc: "",
unit: "",
alt: false,
},
{
name: "keys keys keys",
opts: []GaugeOptionApplier{
WithKeys(key("foo"), key("foo2")),
WithKeys(key("bar"), key("bar2")),
WithKeys(key("baz"), key("baz2")),
},
keys: []core.Key{
key("foo"), key("foo2"),
key("bar"), key("bar2"),
key("baz"), key("baz2"),
},
desc: "",
unit: "",
alt: false,
},
{
name: "description",
opts: []GaugeOptionApplier{
WithDescription("stuff"),
},
keys: nil,
desc: "stuff",
unit: "",
alt: false,
},
{
name: "description override",
opts: []GaugeOptionApplier{
WithDescription("stuff"),
WithDescription("things"),
},
keys: nil,
desc: "things",
unit: "",
alt: false,
},
{
name: "unit",
opts: []GaugeOptionApplier{
WithUnit("s"),
},
keys: nil,
desc: "",
unit: "s",
alt: false,
},
{
name: "unit override",
opts: []GaugeOptionApplier{
WithUnit("s"),
WithUnit("h"),
},
keys: nil,
desc: "",
unit: "h",
alt: false,
},
{
name: "monotonic",
opts: []GaugeOptionApplier{
WithMonotonic(true),
},
keys: nil,
desc: "",
unit: "",
alt: true,
},
{
name: "monotonic, but not really",
opts: []GaugeOptionApplier{
WithMonotonic(true),
WithMonotonic(false),
},
keys: nil,
desc: "",
unit: "",
alt: false,
},
}
checkGaugeDescriptor := func(tt testcase, vk ValueKind, d *Descriptor) {
e := descriptor{
name: tt.name,
keys: tt.keys,
desc: tt.desc,
unit: tt.unit,
alt: tt.alt,
kind: GaugeKind,
vk: vk,
}
checkDescriptor(t, e, d)
}
for idx, tt := range testcases {
t.Logf("Testing gauge case %s (%d)", tt.name, idx)
f := NewFloat64Gauge(tt.name, tt.opts...)
checkGaugeDescriptor(tt, Float64ValueKind, f.Descriptor())
i := NewInt64Gauge(tt.name, tt.opts...)
checkGaugeDescriptor(tt, Int64ValueKind, i.Descriptor())
}
}
func TestMeasureOptions(t *testing.T) {
type testcase struct {
name string
opts []MeasureOptionApplier
keys []core.Key
desc string
unit unit.Unit
alt bool
}
testcases := []testcase{
{
name: "no opts",
opts: nil,
keys: nil,
desc: "",
unit: "",
alt: false,
},
{
name: "keys keys keys",
opts: []MeasureOptionApplier{
WithKeys(key("foo"), key("foo2")),
WithKeys(key("bar"), key("bar2")),
WithKeys(key("baz"), key("baz2")),
},
keys: []core.Key{
key("foo"), key("foo2"),
key("bar"), key("bar2"),
key("baz"), key("baz2"),
},
desc: "",
unit: "",
alt: false,
},
{
name: "description",
opts: []MeasureOptionApplier{
WithDescription("stuff"),
},
keys: nil,
desc: "stuff",
unit: "",
alt: false,
},
{
name: "description override",
opts: []MeasureOptionApplier{
WithDescription("stuff"),
WithDescription("things"),
},
keys: nil,
desc: "things",
unit: "",
alt: false,
},
{
name: "unit",
opts: []MeasureOptionApplier{
WithUnit("s"),
},
keys: nil,
desc: "",
unit: "s",
alt: false,
},
{
name: "unit override",
opts: []MeasureOptionApplier{
WithUnit("s"),
WithUnit("h"),
},
keys: nil,
desc: "",
unit: "h",
alt: false,
},
{
name: "signed",
opts: []MeasureOptionApplier{
WithSigned(true),
},
keys: nil,
desc: "",
unit: "",
alt: true,
},
{
name: "signed, but not really",
opts: []MeasureOptionApplier{
WithSigned(true),
WithSigned(false),
},
keys: nil,
desc: "",
unit: "",
alt: false,
},
}
checkMeasureDescriptor := func(tt testcase, vk ValueKind, d *Descriptor) {
e := descriptor{
name: tt.name,
keys: tt.keys,
desc: tt.desc,
unit: tt.unit,
alt: tt.alt,
kind: MeasureKind,
vk: vk,
}
checkDescriptor(t, e, d)
}
for idx, tt := range testcases {
t.Logf("Testing measure case %s (%d)", tt.name, idx)
f := NewFloat64Measure(tt.name, tt.opts...)
checkMeasureDescriptor(tt, Float64ValueKind, f.Descriptor())
i := NewInt64Measure(tt.name, tt.opts...)
checkMeasureDescriptor(tt, Int64ValueKind, i.Descriptor())
}
}
func TestObserverOptions(t *testing.T) {
type testcase struct {
name string
opts []GaugeOptionApplier
keys []core.Key
desc string
unit unit.Unit
alt bool
}
testcases := []testcase{
{
name: "no opts",
opts: nil,
keys: nil,
desc: "",
unit: "",
alt: false,
},
{
name: "keys keys keys",
opts: []GaugeOptionApplier{
WithKeys(key("foo"), key("foo2")),
WithKeys(key("bar"), key("bar2")),
WithKeys(key("baz"), key("baz2")),
},
keys: []core.Key{
key("foo"), key("foo2"),
key("bar"), key("bar2"),
key("baz"), key("baz2"),
},
desc: "",
unit: "",
alt: false,
},
{
name: "description",
opts: []GaugeOptionApplier{
WithDescription("stuff"),
},
keys: nil,
desc: "stuff",
unit: "",
alt: false,
},
{
name: "description override",
opts: []GaugeOptionApplier{
WithDescription("stuff"),
WithDescription("things"),
},
keys: nil,
desc: "things",
unit: "",
alt: false,
},
{
name: "unit",
opts: []GaugeOptionApplier{
WithUnit("s"),
},
keys: nil,
desc: "",
unit: "s",
alt: false,
},
{
name: "unit override",
opts: []GaugeOptionApplier{
WithUnit("s"),
WithUnit("h"),
},
keys: nil,
desc: "",
unit: "h",
alt: false,
},
{
name: "monotonic",
opts: []GaugeOptionApplier{
WithMonotonic(true),
},
keys: nil,
desc: "",
unit: "",
alt: true,
},
{
name: "monotonic, but not really",
opts: []GaugeOptionApplier{
WithMonotonic(true),
WithMonotonic(false),
},
keys: nil,
desc: "",
unit: "",
alt: false,
},
}
checkObserverDescriptor := func(tt testcase, vk ValueKind, d *Descriptor) {
e := descriptor{
name: tt.name,
keys: tt.keys,
desc: tt.desc,
unit: tt.unit,
alt: tt.alt,
kind: ObserverKind,
vk: vk,
}
checkDescriptor(t, e, d)
}
for idx, tt := range testcases {
t.Logf("Testing observer case %s (%d)", tt.name, idx)
f := NewFloat64Observer(tt.name, tt.opts...)
checkObserverDescriptor(tt, Float64ValueKind, f.Descriptor())
i := NewInt64Observer(tt.name, tt.opts...)
checkObserverDescriptor(tt, Int64ValueKind, i.Descriptor())
}
}
func key(name string) core.Key {
return core.Key{
Name: name,
}
}
type descriptor struct {
name string
keys []core.Key
desc string
unit unit.Unit
alt bool
kind Kind
vk ValueKind
}
func checkDescriptor(t *testing.T, e descriptor, d *Descriptor) {
if e.name != d.Name() {
t.Errorf("Expected name %q, got %q", e.name, d.Name())
}
if len(e.keys) != len(d.Keys()) {
t.Errorf("Expected %d key(s), got %d", len(e.keys), len(d.Keys()))
}
minLen := len(e.keys)
if minLen > len(d.Keys()) {
minLen = len(d.Keys())
}
for i := 0; i < minLen; i++ {
if e.keys[i].Name != d.Keys()[i].Name {
t.Errorf("Expected key %q, got %q", e.keys[i].Name, d.Keys()[i].Name)
}
}
if e.desc != d.Description() {
t.Errorf("Expected description %q, got %q", e.desc, d.Description())
}
if e.unit != d.Unit() {
t.Errorf("Expected unit %q, got %q", e.unit, d.Unit())
}
if e.alt != d.Alternate() {
t.Errorf("Expected alternate %v, got %v", e.alt, d.Alternate())
}
if e.vk != d.ValueKind() {
t.Errorf("Expected value kind %q, got %q", e.vk, d.ValueKind())
}
if e.kind != d.Kind() {
t.Errorf("Expected kind %q, got %q", e.kind, d.Kind())
}
}
func TestCounter(t *testing.T) {
{
c := NewFloat64Counter("ajwaj")
meter := newMockMeter()
ctx := context.Background()
labels := meter.DefineLabels(ctx)
c.Add(ctx, 42, labels)
handle := c.GetHandle(labels)
handle.Add(ctx, 42)
meter.RecordBatch(ctx, labels, c.Measurement(42))
t.Log("Testing float counter")
checkBatches(t, ctx, labels, meter, c.Descriptor())
}
{
c := NewInt64Counter("ajwaj")
meter := newMockMeter()
ctx := context.Background()
labels := meter.DefineLabels(ctx)
c.Add(ctx, 42, labels)
handle := c.GetHandle(labels)
handle.Add(ctx, 42)
meter.RecordBatch(ctx, labels, c.Measurement(42))
t.Log("Testing int counter")
checkBatches(t, ctx, labels, meter, c.Descriptor())
}
}
func TestGauge(t *testing.T) {
{
g := NewFloat64Gauge("ajwaj")
meter := newMockMeter()
ctx := context.Background()
labels := meter.DefineLabels(ctx)
g.Set(ctx, 42, labels)
handle := g.GetHandle(labels)
handle.Set(ctx, 42)
meter.RecordBatch(ctx, labels, g.Measurement(42))
t.Log("Testing float gauge")
checkBatches(t, ctx, labels, meter, g.Descriptor())
}
{
g := NewInt64Gauge("ajwaj")
meter := newMockMeter()
ctx := context.Background()
labels := meter.DefineLabels(ctx)
g.Set(ctx, 42, labels)
handle := g.GetHandle(labels)
handle.Set(ctx, 42)
meter.RecordBatch(ctx, labels, g.Measurement(42))
t.Log("Testing int gauge")
checkBatches(t, ctx, labels, meter, g.Descriptor())
}
}
func TestMeasure(t *testing.T) {
{
m := NewFloat64Measure("ajwaj")
meter := newMockMeter()
ctx := context.Background()
labels := meter.DefineLabels(ctx)
m.Record(ctx, 42, labels)
handle := m.GetHandle(labels)
handle.Record(ctx, 42)
meter.RecordBatch(ctx, labels, m.Measurement(42))
t.Log("Testing float measure")
checkBatches(t, ctx, labels, meter, m.Descriptor())
}
{
m := NewInt64Measure("ajwaj")
meter := newMockMeter()
ctx := context.Background()
labels := meter.DefineLabels(ctx)
m.Record(ctx, 42, labels)
handle := m.GetHandle(labels)
handle.Record(ctx, 42)
meter.RecordBatch(ctx, labels, m.Measurement(42))
t.Log("Testing int measure")
checkBatches(t, ctx, labels, meter, m.Descriptor())
}
}
func TestObserver(t *testing.T) {
{
o := NewFloat64Observer("ajwaj")
meter := newMockMeter()
ctx := context.Background()
labels := meter.DefineLabels(ctx)
RegisterFloat64Observer(meter, o, func(meter Meter, o Float64Observer, cb Float64ObservationCallback) {
cb(labels, 42)
cb(labels, 42)
cb(labels, 42)
})
meter.PerformObservations()
t.Log("Testing float observer")
checkBatches(t, ctx, labels, meter, o.Descriptor())
}
{
o := NewInt64Observer("ajwaj")
ctx := context.Background()
meter := newMockMeter()
labels := meter.DefineLabels(ctx)
RegisterInt64Observer(meter, o, func(meter Meter, o Int64Observer, cb Int64ObservationCallback) {
cb(labels, 42)
cb(labels, 42)
cb(labels, 42)
})
meter.PerformObservations()
t.Log("Testing int observer")
checkBatches(t, ctx, labels, meter, o.Descriptor())
}
}
func checkBatches(t *testing.T, ctx context.Context, labels LabelSet, meter *mockMeter, descriptor *Descriptor) {
if len(meter.measurementBatches) != 3 {
t.Errorf("Expected 3 recorded measurement batches, got %d", len(meter.measurementBatches))
}
ourLabelSet := labels.(*mockLabelSet)
minLen := 3
if minLen > len(meter.measurementBatches) {
minLen = len(meter.measurementBatches)
}
for i := 0; i < minLen; i++ {
got := meter.measurementBatches[i]
if got.ctx != ctx {
d := func(c context.Context) string {
return fmt.Sprintf("(ptr: %p, ctx %#v)", c, c)
}
t.Errorf("Wrong recorded context in batch %d, expected %s, got %s", i, d(ctx), d(got.ctx))
}
if got.labelSet != ourLabelSet {
d := func(l *mockLabelSet) string {
return fmt.Sprintf("(ptr: %p, labels %#v)", l, l.labels)
}
t.Errorf("Wrong recorded label set in batch %d, expected %s, got %s", i, d(ourLabelSet), d(got.labelSet))
}
if len(got.measurements) != 1 {
t.Errorf("Expected 1 measurement in batch %d, got %d", i, len(got.measurements))
}
minMLen := 1
if minMLen > len(got.measurements) {
minMLen = len(got.measurements)
}
for j := 0; j < minMLen; j++ {
measurement := got.measurements[j]
if measurement.Descriptor != descriptor {
d := func(d *Descriptor) string {
return fmt.Sprintf("(ptr: %p, descriptor %#v)", d, d)
}
t.Errorf("Wrong recorded descriptor in measurement %d in batch %d, expected %s, got %s", j, i, d(descriptor), d(measurement.Descriptor))
}
ft := fortyTwo(t, descriptor.ValueKind())
if measurement.Value.RawCompare(ft.AsRaw(), descriptor.ValueKind()) != 0 {
t.Errorf("Wrong recorded value in measurement %d in batch %d, expected %s, got %s", j, i, ft.Emit(descriptor.ValueKind()), measurement.Value.Emit(descriptor.ValueKind()))
}
}
}
}
func fortyTwo(t *testing.T, kind ValueKind) MeasurementValue {
switch kind {
case Int64ValueKind:
return NewInt64MeasurementValue(42)
case Float64ValueKind:
return NewFloat64MeasurementValue(42)
}
t.Errorf("Invalid value kind %q", kind)
return NewInt64MeasurementValue(0)
}

View File

@@ -23,43 +23,48 @@ var (
descriptorID uint64
)
// TODO: Maybe unexport that and document very _very_ clearly, that
// you can still get a descriptor with NewInt64Counter(…).Descriptor
// CommonMetric holds a descriptor. It is used mostly to implement the
// common parts for every metric kind.
type CommonMetric struct {
*Descriptor
type commonMetric struct {
d *Descriptor
}
func (m CommonMetric) getHandle(labels LabelSet) Handle {
return labels.Meter().NewHandle(m.Descriptor, labels)
var _ ExplicitReportingMetric = commonMetric{}
func (m commonMetric) Descriptor() *Descriptor {
return m.d
}
func (m CommonMetric) float64Measurement(value float64) Measurement {
func (m commonMetric) SupportHandle() hiddenType {
return hiddenType{}
}
func (m commonMetric) getHandle(labels LabelSet) Handle {
return labels.Meter().NewHandle(m, labels)
}
func (m commonMetric) float64Measurement(value float64) Measurement {
return Measurement{
Descriptor: m.Descriptor,
Descriptor: m.d,
Value: NewFloat64MeasurementValue(value),
}
}
func (m CommonMetric) int64Measurement(value int64) Measurement {
func (m commonMetric) int64Measurement(value int64) Measurement {
return Measurement{
Descriptor: m.Descriptor,
Descriptor: m.d,
Value: NewInt64MeasurementValue(value),
}
}
func (m CommonMetric) recordOne(ctx context.Context, value MeasurementValue, labels LabelSet) {
func (m commonMetric) recordOne(ctx context.Context, value MeasurementValue, labels LabelSet) {
labels.Meter().RecordBatch(ctx, labels, Measurement{
Descriptor: m.Descriptor,
Descriptor: m.d,
Value: value,
})
}
func registerCommonMetric(name string, kind Kind, valueKind ValueKind) CommonMetric {
return CommonMetric{
Descriptor: registerDescriptor(name, kind, valueKind),
func registerCommonMetric(name string, kind Kind, valueKind ValueKind) commonMetric {
return commonMetric{
d: registerDescriptor(name, kind, valueKind),
}
}

View File

@@ -20,12 +20,12 @@ import (
// Float64Counter is a metric that accumulates float64 values.
type Float64Counter struct {
CommonMetric
commonMetric
}
// Int64Counter is a metric that accumulates int64 values.
type Int64Counter struct {
CommonMetric
commonMetric
}
// Float64CounterHandle is a handle for Float64Counter.
@@ -50,43 +50,55 @@ type counterOptionWrapper struct {
F Option
}
var _ CounterOptionApplier = counterOptionWrapper{}
var (
_ CounterOptionApplier = counterOptionWrapper{}
_ ExplicitReportingMetric = Float64Counter{}
_ ExplicitReportingMetric = Int64Counter{}
)
func (o counterOptionWrapper) ApplyCounterOption(d *Descriptor) {
o.F(d)
}
func newCounter(name string, valueKind ValueKind, mos ...CounterOptionApplier) CommonMetric {
func newCounter(name string, valueKind ValueKind, mos ...CounterOptionApplier) commonMetric {
m := registerCommonMetric(name, CounterKind, valueKind)
for _, opt := range mos {
opt.ApplyCounterOption(m.Descriptor)
opt.ApplyCounterOption(m.Descriptor())
}
return m
}
// NewFloat64Counter creates a new counter for float64.
func NewFloat64Counter(name string, mos ...CounterOptionApplier) (c Float64Counter) {
c.CommonMetric = newCounter(name, Float64ValueKind, mos...)
c.commonMetric = newCounter(name, Float64ValueKind, mos...)
return
}
// NewInt64Counter creates a new counter for int64.
func NewInt64Counter(name string, mos ...CounterOptionApplier) (c Int64Counter) {
c.CommonMetric = newCounter(name, Int64ValueKind, mos...)
c.commonMetric = newCounter(name, Int64ValueKind, mos...)
return
}
// GetHandle creates a handle for this counter. The labels should
// contain the keys and values specified in the counter with the
// WithKeys option.
// contain the keys and values for each key specified in the counter
// with the WithKeys option.
//
// If the labels do not contain a value for the key specified in the
// counter with the WithKeys option, then the missing value will be
// treated as unspecified.
func (c *Float64Counter) GetHandle(labels LabelSet) (h Float64CounterHandle) {
h.Handle = c.getHandle(labels)
return
}
// GetHandle creates a handle for this counter. The labels should
// contain the keys and values specified in the counter with the
// WithKeys option.
// contain the keys and values for each key specified in the counter
// with the WithKeys option.
//
// If the labels do not contain a value for the key specified in the
// counter with the WithKeys option, then the missing value will be
// treated as unspecified.
func (c *Int64Counter) GetHandle(labels LabelSet) (h Int64CounterHandle) {
h.Handle = c.getHandle(labels)
return
@@ -105,15 +117,23 @@ func (c *Int64Counter) Measurement(value int64) Measurement {
}
// Add adds the value to the counter's sum. The labels should contain
// the keys and values specified in the counter with the WithKeys
// option.
// the keys and values for each key specified in the counter with the
// WithKeys option.
//
// If the labels do not contain a value for the key specified in the
// 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)
}
// Add adds the value to the counter's sum. The labels should contain
// the keys and values specified in the counter with the WithKeys
// option.
// the keys and values for each key specified in the counter with the
// WithKeys option.
//
// If the labels do not contain a value for the key specified in the
// 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)
}

View File

@@ -20,12 +20,12 @@ import (
// Float64Gauge is a metric that stores the last float64 value.
type Float64Gauge struct {
CommonMetric
commonMetric
}
// Int64Gauge is a metric that stores the last int64 value.
type Int64Gauge struct {
CommonMetric
commonMetric
}
// Float64GaugeHandle is a handle for Float64Gauge.
@@ -50,43 +50,55 @@ type gaugeOptionWrapper struct {
F Option
}
var _ GaugeOptionApplier = gaugeOptionWrapper{}
var (
_ GaugeOptionApplier = gaugeOptionWrapper{}
_ ExplicitReportingMetric = Float64Gauge{}
_ ExplicitReportingMetric = Int64Gauge{}
)
func (o gaugeOptionWrapper) ApplyGaugeOption(d *Descriptor) {
o.F(d)
}
func newGauge(name string, valueKind ValueKind, mos ...GaugeOptionApplier) CommonMetric {
func newGauge(name string, valueKind ValueKind, mos ...GaugeOptionApplier) commonMetric {
m := registerCommonMetric(name, GaugeKind, valueKind)
for _, opt := range mos {
opt.ApplyGaugeOption(m.Descriptor)
opt.ApplyGaugeOption(m.Descriptor())
}
return m
}
// NewFloat64Gauge creates a new gauge for float64.
func NewFloat64Gauge(name string, mos ...GaugeOptionApplier) (g Float64Gauge) {
g.CommonMetric = newGauge(name, Float64ValueKind, mos...)
g.commonMetric = newGauge(name, Float64ValueKind, mos...)
return
}
// NewInt64Gauge creates a new gauge for int64.
func NewInt64Gauge(name string, mos ...GaugeOptionApplier) (g Int64Gauge) {
g.CommonMetric = newGauge(name, Int64ValueKind, mos...)
g.commonMetric = newGauge(name, Int64ValueKind, mos...)
return
}
// GetHandle creates a handle for this gauge. The labels should
// contain the keys and values specified in the gauge with the
// WithKeys option.
// contain the keys and values for each key specified in the gauge
// with the WithKeys option.
//
// If the labels do not contain a value for the key specified in the
// gauge with the WithKeys option, then the missing value will be
// treated as unspecified.
func (g *Float64Gauge) GetHandle(labels LabelSet) (h Float64GaugeHandle) {
h.Handle = g.getHandle(labels)
return
}
// GetHandle creates a handle for this gauge. The labels should
// contain the keys and values specified in the gauge with the
// WithKeys option.
// contain the keys and values for each key specified in the gauge
// with the WithKeys option.
//
// If the labels do not contain a value for the key specified in the
// gauge with the WithKeys option, then the missing value will be
// treated as unspecified.
func (g *Int64Gauge) GetHandle(labels LabelSet) (h Int64GaugeHandle) {
h.Handle = g.getHandle(labels)
return
@@ -104,26 +116,34 @@ func (g *Int64Gauge) Measurement(value int64) Measurement {
return g.int64Measurement(value)
}
// Set sets the value of the gauge to the passed value. The labels
// should contain the keys and values specified in the gauge with the
// WithKeys option.
// Set assigns the passed value to the value of the gauge. The labels
// should contain the keys and values for each key specified in the
// gauge with the WithKeys option.
//
// If the labels do not contain a value for the key specified in the
// 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)
}
// Set sets the value of the gauge to the passed value. The labels
// should contain the keys and values specified in the gauge with the
// WithKeys option.
// Set assigns the passed value to the value of the gauge. The labels
// should contain the keys and values for each key specified in the
// gauge with the WithKeys option.
//
// If the labels do not contain a value for the key specified in the
// 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)
}
// Set sets the value of the gauge to the passed value.
// 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))
}
// Set sets the value of the gauge to the passed 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))
}

33
api/metric/global_test.go Normal file
View File

@@ -0,0 +1,33 @@
// 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"
)
func TestGlobalMeter(t *testing.T) {
m := GlobalMeter()
if _, ok := m.(noopMeter); !ok {
t.Errorf("Expected global meter to be a noopMeter instance, got an instance of %T", m)
}
SetGlobalMeter(newMockMeter())
m = GlobalMeter()
if _, ok := m.(*mockMeter); !ok {
t.Errorf("Expected global meter to be a *mockMetric.MockMeter instance, got an instance of %T", m)
}
}

View File

@@ -20,12 +20,12 @@ import (
// Float64Measure is a metric that records float64 values.
type Float64Measure struct {
CommonMetric
commonMetric
}
// Int64Measure is a metric that records int64 values.
type Int64Measure struct {
CommonMetric
commonMetric
}
// Float64MeasureHandle is a handle for Float64Measure.
@@ -50,43 +50,55 @@ type measureOptionWrapper struct {
F Option
}
var _ MeasureOptionApplier = measureOptionWrapper{}
var (
_ MeasureOptionApplier = measureOptionWrapper{}
_ ExplicitReportingMetric = Float64Measure{}
_ ExplicitReportingMetric = Int64Measure{}
)
func (o measureOptionWrapper) ApplyMeasureOption(d *Descriptor) {
o.F(d)
}
func newMeasure(name string, valueKind ValueKind, mos ...MeasureOptionApplier) CommonMetric {
func newMeasure(name string, valueKind ValueKind, mos ...MeasureOptionApplier) commonMetric {
m := registerCommonMetric(name, MeasureKind, valueKind)
for _, opt := range mos {
opt.ApplyMeasureOption(m.Descriptor)
opt.ApplyMeasureOption(m.Descriptor())
}
return m
}
// NewFloat64Measure creates a new measure for float64.
func NewFloat64Measure(name string, mos ...MeasureOptionApplier) (c Float64Measure) {
c.CommonMetric = newMeasure(name, Float64ValueKind, mos...)
c.commonMetric = newMeasure(name, Float64ValueKind, mos...)
return
}
// NewInt64Measure creates a new measure for int64.
func NewInt64Measure(name string, mos ...MeasureOptionApplier) (c Int64Measure) {
c.CommonMetric = newMeasure(name, Int64ValueKind, mos...)
c.commonMetric = newMeasure(name, Int64ValueKind, mos...)
return
}
// GetHandle creates a handle for this measure. The labels should
// contain the keys and values specified in the measure with the
// WithKeys option.
// contain the keys and values for each key specified in the measure
// with the WithKeys option.
//
// If the labels do not contain a value for the key specified in the
// measure with the WithKeys option, then the missing value will be
// treated as unspecified.
func (c *Float64Measure) GetHandle(labels LabelSet) (h Float64MeasureHandle) {
h.Handle = c.getHandle(labels)
return
}
// GetHandle creates a handle for this measure. The labels should
// contain the keys and values specified in the measure with the
// WithKeys option.
// contain the keys and values for each key specified in the measure
// with the WithKeys option.
//
// If the labels do not contain a value for the key specified in the
// measure with the WithKeys option, then the missing value will be
// treated as unspecified.
func (c *Int64Measure) GetHandle(labels LabelSet) (h Int64MeasureHandle) {
h.Handle = c.getHandle(labels)
return
@@ -105,15 +117,23 @@ func (c *Int64Measure) Measurement(value int64) Measurement {
}
// Record adds a new value to the list of measure's records. The
// labels should contain the keys and values specified in the measure
// with the WithKeys option.
// labels should contain the keys and values for each key specified in
// the measure with the WithKeys option.
//
// If the labels do not contain a value for the key specified in the
// 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)
}
// Record adds a new value to the list of measure's records. The
// labels should contain the keys and values specified in the measure
// with the WithKeys option.
// labels should contain the keys and values for each key specified in
// the measure with the WithKeys option.
//
// If the labels do not contain a value for the key specified in the
// 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)
}

141
api/metric/mock_test.go Normal file
View File

@@ -0,0 +1,141 @@
// 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 (
"context"
"go.opentelemetry.io/api/core"
)
type (
mockHandle struct {
descriptor *Descriptor
labelSet *mockLabelSet
}
mockLabelSet struct {
meter *mockMeter
labels map[core.Key]core.Value
}
batch struct {
ctx context.Context
labelSet *mockLabelSet
measurements []Measurement
}
observerData struct {
observer Observer
callback ObserverCallback
}
observerMap map[DescriptorID]observerData
mockMeter struct {
measurementBatches []batch
observers observerMap
}
)
var (
_ Handle = &mockHandle{}
_ LabelSet = &mockLabelSet{}
_ Meter = &mockMeter{}
)
func (h *mockHandle) RecordOne(ctx context.Context, value MeasurementValue) {
h.labelSet.meter.RecordBatch(ctx, h.labelSet, Measurement{
Descriptor: h.descriptor,
Value: value,
})
}
func (s *mockLabelSet) Meter() Meter {
return s.meter
}
func newMockMeter() *mockMeter {
return &mockMeter{}
}
func (m *mockMeter) DefineLabels(ctx context.Context, labels ...core.KeyValue) LabelSet {
ul := make(map[core.Key]core.Value)
for _, kv := range labels {
ul[kv.Key] = kv.Value
}
return &mockLabelSet{
meter: m,
labels: ul,
}
}
func (m *mockMeter) RecordBatch(ctx context.Context, labels LabelSet, measurements ...Measurement) {
ourLabelSet := labels.(*mockLabelSet)
m.measurementBatches = append(m.measurementBatches, batch{
ctx: ctx,
labelSet: ourLabelSet,
measurements: measurements,
})
}
func (m *mockMeter) NewHandle(erm ExplicitReportingMetric, labels LabelSet) Handle {
descriptor := erm.Descriptor()
ourLabels := labels.(*mockLabelSet)
return &mockHandle{
descriptor: descriptor,
labelSet: ourLabels,
}
}
func (m *mockMeter) DeleteHandle(Handle) {
}
func (m *mockMeter) RegisterObserver(o Observer, cb ObserverCallback) {
id := o.Descriptor().ID()
if _, ok := m.observers[id]; ok {
return
}
data := observerData{
observer: o,
callback: cb,
}
if m.observers == nil {
m.observers = observerMap{
id: data,
}
} else {
m.observers[id] = data
}
}
func (m *mockMeter) UnregisterObserver(o Observer) {
delete(m.observers, o.Descriptor().ID())
}
func (m *mockMeter) PerformObservations() {
for _, data := range m.observers {
o := data.observer
descriptor := o.Descriptor()
ocb := func(l LabelSet, v MeasurementValue) {
m.RecordBatch(context.Background(), l, Measurement{
Descriptor: descriptor,
Value: v,
})
}
data.callback(m, o, ocb)
}
}

View File

@@ -25,7 +25,7 @@ func (noopMeter) DefineLabels(context.Context, ...core.KeyValue) LabelSet {
return noopLabelSet{}
}
func (noopMeter) NewHandle(*Descriptor, LabelSet) Handle {
func (noopMeter) NewHandle(ExplicitReportingMetric, LabelSet) Handle {
return noopHandle{}
}

View File

@@ -16,7 +16,7 @@ package metric
// Observer is a base of typed-observers. Shouldn't be used directly.
type Observer struct {
*Descriptor
d *Descriptor
}
// Float64Observer is an observer that reports float64 values.
@@ -30,9 +30,9 @@ type Int64Observer struct {
}
func newObserver(name string, valueKind ValueKind, mos ...GaugeOptionApplier) (o Observer) {
o.Descriptor = registerDescriptor(name, ObserverKind, valueKind)
o.d = registerDescriptor(name, ObserverKind, valueKind)
for _, opt := range mos {
opt.ApplyGaugeOption(o.Descriptor)
opt.ApplyGaugeOption(o.d)
}
return
}
@@ -48,3 +48,7 @@ func NewInt64Observer(name string, mos ...GaugeOptionApplier) (o Int64Observer)
o.Observer = newObserver(name, Int64ValueKind, mos...)
return
}
func (o Observer) Descriptor() *Descriptor {
return o.d
}

View File

@@ -60,11 +60,11 @@ func (s *sdk) DefineLabels(ctx context.Context, labels ...core.KeyValue) metric.
}
}
func (s *sdk) NewHandle(descriptor *metric.Descriptor, labels metric.LabelSet) metric.Handle {
func (s *sdk) NewHandle(erm metric.ExplicitReportingMetric, labels metric.LabelSet) metric.Handle {
mlabels, _ := labels.(metricLabels)
return &metricHandle{
descriptor: descriptor,
descriptor: erm.Descriptor(),
labels: mlabels,
}
}
@@ -100,7 +100,7 @@ func (s *sdk) insertNewObserver(observer metric.Observer, callback metric.Observ
s.observersLock.Lock()
defer s.observersLock.Unlock()
old := s.loadObserversMap()
id := observer.Descriptor.ID()
id := observer.Descriptor().ID()
if _, ok := old[id]; ok {
return false
}
@@ -120,7 +120,7 @@ func (s *sdk) UnregisterObserver(observer metric.Observer) {
s.observersLock.Lock()
defer s.observersLock.Unlock()
old := s.loadObserversMap()
id := observer.Descriptor.ID()
id := observer.Descriptor().ID()
if _, ok := old[id]; !ok {
return
}
@@ -146,7 +146,7 @@ func (s *sdk) observersRoutine() {
return
}
for _, data := range m {
ocb := s.getObservationCallback(data.observer.Descriptor)
ocb := s.getObservationCallback(data.observer.Descriptor())
data.callback(s, data.observer, ocb)
}
}