opentelemetry-go/api/metric/api_test.go

311 lines
8.5 KiB
Go

// 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"
"errors"
"fmt"
"testing"
"go.opentelemetry.io/otel/api/core"
"go.opentelemetry.io/otel/api/key"
"go.opentelemetry.io/otel/api/metric"
"go.opentelemetry.io/otel/api/unit"
mock "go.opentelemetry.io/otel/internal/metric"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var Must = metric.Must
func TestOptions(t *testing.T) {
type testcase struct {
name string
opts []metric.Option
keys []core.Key
desc string
unit unit.Unit
}
testcases := []testcase{
{
name: "no opts",
opts: nil,
keys: nil,
desc: "",
unit: "",
},
{
name: "keys keys keys",
opts: []metric.Option{
metric.WithKeys(key.New("foo"), key.New("foo2")),
metric.WithKeys(key.New("bar"), key.New("bar2")),
metric.WithKeys(key.New("baz"), key.New("baz2")),
},
keys: []core.Key{
key.New("foo"), key.New("foo2"),
key.New("bar"), key.New("bar2"),
key.New("baz"), key.New("baz2"),
},
desc: "",
unit: "",
},
{
name: "description",
opts: []metric.Option{
metric.WithDescription("stuff"),
},
keys: nil,
desc: "stuff",
unit: "",
},
{
name: "description override",
opts: []metric.Option{
metric.WithDescription("stuff"),
metric.WithDescription("things"),
},
keys: nil,
desc: "things",
unit: "",
},
{
name: "unit",
opts: []metric.Option{
metric.WithUnit("s"),
},
keys: nil,
desc: "",
unit: "s",
},
{
name: "unit override",
opts: []metric.Option{
metric.WithUnit("s"),
metric.WithUnit("h"),
},
keys: nil,
desc: "",
unit: "h",
},
}
for idx, tt := range testcases {
t.Logf("Testing counter case %s (%d)", tt.name, idx)
if diff := cmp.Diff(metric.Configure(tt.opts), metric.Config{
Description: tt.desc,
Unit: tt.unit,
Keys: tt.keys,
}); diff != "" {
t.Errorf("Compare options: -got +want %s", diff)
}
}
}
func TestCounter(t *testing.T) {
{
meter := mock.NewMeter()
c := Must(meter).NewFloat64Counter("test.counter.float")
ctx := context.Background()
labels := meter.Labels()
c.Add(ctx, 42, labels)
boundInstrument := c.Bind(labels)
boundInstrument.Add(ctx, 42)
meter.RecordBatch(ctx, labels, c.Measurement(42))
t.Log("Testing float counter")
checkBatches(t, ctx, labels, meter, core.Float64NumberKind, c.Impl())
}
{
meter := mock.NewMeter()
c := Must(meter).NewInt64Counter("test.counter.int")
ctx := context.Background()
labels := meter.Labels()
c.Add(ctx, 42, labels)
boundInstrument := c.Bind(labels)
boundInstrument.Add(ctx, 42)
meter.RecordBatch(ctx, labels, c.Measurement(42))
t.Log("Testing int counter")
checkBatches(t, ctx, labels, meter, core.Int64NumberKind, c.Impl())
}
}
func TestMeasure(t *testing.T) {
{
meter := mock.NewMeter()
m := Must(meter).NewFloat64Measure("test.measure.float")
ctx := context.Background()
labels := meter.Labels()
m.Record(ctx, 42, labels)
boundInstrument := m.Bind(labels)
boundInstrument.Record(ctx, 42)
meter.RecordBatch(ctx, labels, m.Measurement(42))
t.Log("Testing float measure")
checkBatches(t, ctx, labels, meter, core.Float64NumberKind, m.Impl())
}
{
meter := mock.NewMeter()
m := Must(meter).NewInt64Measure("test.measure.int")
ctx := context.Background()
labels := meter.Labels()
m.Record(ctx, 42, labels)
boundInstrument := m.Bind(labels)
boundInstrument.Record(ctx, 42)
meter.RecordBatch(ctx, labels, m.Measurement(42))
t.Log("Testing int measure")
checkBatches(t, ctx, labels, meter, core.Int64NumberKind, m.Impl())
}
}
func TestObserver(t *testing.T) {
{
meter := mock.NewMeter()
labels := meter.Labels()
o := Must(meter).RegisterFloat64Observer("test.observer.float", func(result metric.Float64ObserverResult) {
result.Observe(42, labels)
})
t.Log("Testing float observer")
meter.RunObservers()
checkObserverBatch(t, labels, meter, core.Float64NumberKind, o)
}
{
meter := mock.NewMeter()
labels := meter.Labels()
o := Must(meter).RegisterInt64Observer("test.observer.int", func(result metric.Int64ObserverResult) {
result.Observe(42, labels)
})
t.Log("Testing int observer")
meter.RunObservers()
checkObserverBatch(t, labels, meter, core.Int64NumberKind, o)
}
}
func checkBatches(t *testing.T, ctx context.Context, labels metric.LabelSet, meter *mock.Meter, kind core.NumberKind, instrument metric.InstrumentImpl) {
t.Helper()
if len(meter.MeasurementBatches) != 3 {
t.Errorf("Expected 3 recorded measurement batches, got %d", len(meter.MeasurementBatches))
}
ourInstrument := instrument.(*mock.Instrument)
ourLabelSet := labels.(*mock.LabelSet)
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 *mock.LabelSet) 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.Instrument != ourInstrument {
d := func(i *mock.Instrument) string {
return fmt.Sprintf("(ptr: %p, instrument %#v)", i, i)
}
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.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 checkObserverBatch(t *testing.T, labels metric.LabelSet, meter *mock.Meter, kind core.NumberKind, observer interface{}) {
t.Helper()
assert.Len(t, meter.MeasurementBatches, 1)
if len(meter.MeasurementBatches) < 1 {
return
}
o := observer.(*mock.Observer)
if !assert.NotNil(t, o) {
return
}
ourLabelSet := labels.(*mock.LabelSet)
got := meter.MeasurementBatches[0]
assert.Equal(t, ourLabelSet, got.LabelSet)
assert.Len(t, got.Measurements, 1)
if len(got.Measurements) < 1 {
return
}
measurement := got.Measurements[0]
assert.Equal(t, o.Instrument, measurement.Instrument)
ft := fortyTwo(t, kind)
assert.Equal(t, 0, measurement.Number.CompareNumber(kind, ft))
}
func fortyTwo(t *testing.T, kind core.NumberKind) core.Number {
t.Helper()
switch kind {
case core.Int64NumberKind:
return core.NewInt64Number(42)
case core.Float64NumberKind:
return core.NewFloat64Number(42)
}
t.Errorf("Invalid value kind %q", kind)
return core.NewInt64Number(0)
}
type testWrappedInst struct{}
func (*testWrappedInst) Bind(labels metric.LabelSet) metric.BoundInstrumentImpl {
panic("Not called")
}
func (*testWrappedInst) RecordOne(ctx context.Context, number core.Number, labels metric.LabelSet) {
panic("Not called")
}
func TestWrappedInstrumentError(t *testing.T) {
i0 := &testWrappedInst{}
e0 := errors.New("Test wrap error")
inst, err := metric.WrapInt64MeasureInstrument(i0, e0)
// Check that error passes through w/o modifying instrument.
require.Equal(t, inst.Impl().(*testWrappedInst), i0)
require.Equal(t, err, e0)
// Check that nil instrument is handled.
inst, err = metric.WrapInt64MeasureInstrument(nil, e0)
require.Equal(t, err, e0)
require.NotNil(t, inst)
require.NotNil(t, inst.Impl())
// Check that nil instrument generates an error.
inst, err = metric.WrapInt64MeasureInstrument(nil, nil)
require.NotNil(t, err)
require.NotNil(t, inst)
require.NotNil(t, inst.Impl())
}