1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2025-07-17 01:12:45 +02:00

Drop the gauge instrument (#537)

* drop gauge instrument

* Restore the benchmark and stress test for lastvalue aggregator, but remove monotonic last-value support

* Rename gauge->lastvalue and remove remaining uses of the word 'gauge'

Co-authored-by: Krzesimir Nowak <krzesimir@kinvolk.io>
This commit is contained in:
Joshua MacDonald
2020-03-10 16:00:37 -07:00
committed by GitHub
parent fe0099fb3d
commit 9674c81cb7
33 changed files with 333 additions and 1048 deletions

View File

@ -14,7 +14,6 @@ import (
sdk "go.opentelemetry.io/otel/sdk/metric" sdk "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/metric/aggregator/counter" "go.opentelemetry.io/otel/sdk/metric/aggregator/counter"
"go.opentelemetry.io/otel/sdk/metric/aggregator/ddsketch" "go.opentelemetry.io/otel/sdk/metric/aggregator/ddsketch"
"go.opentelemetry.io/otel/sdk/metric/aggregator/gauge"
"go.opentelemetry.io/otel/sdk/metric/aggregator/minmaxsumcount" "go.opentelemetry.io/otel/sdk/metric/aggregator/minmaxsumcount"
sdktrace "go.opentelemetry.io/otel/sdk/trace" sdktrace "go.opentelemetry.io/otel/sdk/trace"
) )
@ -41,8 +40,6 @@ func (*benchFixture) AggregatorFor(descriptor *export.Descriptor) export.Aggrega
switch descriptor.MetricKind() { switch descriptor.MetricKind() {
case export.CounterKind: case export.CounterKind:
return counter.New() return counter.New()
case export.GaugeKind:
return gauge.New()
case export.MeasureKind: case export.MeasureKind:
if strings.HasSuffix(descriptor.Name(), "minmaxsumcount") { if strings.HasSuffix(descriptor.Name(), "minmaxsumcount") {
return minmaxsumcount.New(descriptor) return minmaxsumcount.New(descriptor)

View File

@ -35,7 +35,6 @@ type metricKind int8
const ( const (
counterKind metricKind = iota counterKind metricKind = iota
gaugeKind
measureKind measureKind
) )
@ -200,11 +199,6 @@ func newInstDelegate(m metric.Meter, name string, mkind metricKind, nkind core.N
return m.NewInt64Counter(name, opts.([]metric.CounterOptionApplier)...).Impl() return m.NewInt64Counter(name, opts.([]metric.CounterOptionApplier)...).Impl()
} }
return m.NewFloat64Counter(name, opts.([]metric.CounterOptionApplier)...).Impl() return m.NewFloat64Counter(name, opts.([]metric.CounterOptionApplier)...).Impl()
case gaugeKind:
if nkind == core.Int64NumberKind {
return m.NewInt64Gauge(name, opts.([]metric.GaugeOptionApplier)...).Impl()
}
return m.NewFloat64Gauge(name, opts.([]metric.GaugeOptionApplier)...).Impl()
case measureKind: case measureKind:
if nkind == core.Int64NumberKind { if nkind == core.Int64NumberKind {
return m.NewInt64Measure(name, opts.([]metric.MeasureOptionApplier)...).Impl() return m.NewInt64Measure(name, opts.([]metric.MeasureOptionApplier)...).Impl()
@ -386,14 +380,6 @@ func (m *meter) NewFloat64Counter(name string, opts ...metric.CounterOptionAppli
return metric.WrapFloat64CounterInstrument(m.newInst(name, counterKind, core.Float64NumberKind, opts)) return metric.WrapFloat64CounterInstrument(m.newInst(name, counterKind, core.Float64NumberKind, opts))
} }
func (m *meter) NewInt64Gauge(name string, opts ...metric.GaugeOptionApplier) metric.Int64Gauge {
return metric.WrapInt64GaugeInstrument(m.newInst(name, gaugeKind, core.Int64NumberKind, opts))
}
func (m *meter) NewFloat64Gauge(name string, opts ...metric.GaugeOptionApplier) metric.Float64Gauge {
return metric.WrapFloat64GaugeInstrument(m.newInst(name, gaugeKind, core.Float64NumberKind, opts))
}
func (m *meter) NewInt64Measure(name string, opts ...metric.MeasureOptionApplier) metric.Int64Measure { func (m *meter) NewInt64Measure(name string, opts ...metric.MeasureOptionApplier) metric.Int64Measure {
return metric.WrapInt64MeasureInstrument(m.newInst(name, measureKind, core.Int64NumberKind, opts)) return metric.WrapInt64MeasureInstrument(m.newInst(name, measureKind, core.Int64NumberKind, opts))
} }

View File

@ -34,10 +34,6 @@ func TestDirect(t *testing.T) {
counter.Add(ctx, 1, labels1) counter.Add(ctx, 1, labels1)
counter.Add(ctx, 1, labels1) counter.Add(ctx, 1, labels1)
gauge := meter1.NewInt64Gauge("test.gauge")
gauge.Set(ctx, 1, labels2)
gauge.Set(ctx, 2, labels2)
measure := meter1.NewFloat64Measure("test.measure") measure := meter1.NewFloat64Measure("test.measure")
measure.Record(ctx, 1, labels1) measure.Record(ctx, 1, labels1)
measure.Record(ctx, 2, labels1) measure.Record(ctx, 2, labels1)
@ -60,13 +56,12 @@ func TestDirect(t *testing.T) {
global.SetMeterProvider(sdk) global.SetMeterProvider(sdk)
counter.Add(ctx, 1, labels1) counter.Add(ctx, 1, labels1)
gauge.Set(ctx, 3, labels2)
measure.Record(ctx, 3, labels1) measure.Record(ctx, 3, labels1)
second.Record(ctx, 3, labels3) second.Record(ctx, 3, labels3)
mock := sdk.Meter("test1").(*metrictest.Meter) mock := sdk.Meter("test1").(*metrictest.Meter)
mock.RunObservers() mock.RunObservers()
require.Len(t, mock.MeasurementBatches, 7) require.Len(t, mock.MeasurementBatches, 6)
require.Equal(t, map[core.Key]core.Value{ require.Equal(t, map[core.Key]core.Value{
lvals1.Key: lvals1.Value, lvals1.Key: lvals1.Value,
@ -78,62 +73,53 @@ func TestDirect(t *testing.T) {
mock.MeasurementBatches[0].Measurements[0].Instrument.Name) mock.MeasurementBatches[0].Measurements[0].Instrument.Name)
require.Equal(t, map[core.Key]core.Value{ require.Equal(t, map[core.Key]core.Value{
lvals2.Key: lvals2.Value, lvals1.Key: lvals1.Value,
}, mock.MeasurementBatches[1].LabelSet.Labels) }, mock.MeasurementBatches[1].LabelSet.Labels)
require.Len(t, mock.MeasurementBatches[1].Measurements, 1) require.Len(t, mock.MeasurementBatches[1].Measurements, 1)
require.Equal(t, int64(3), require.InDelta(t, float64(3),
mock.MeasurementBatches[1].Measurements[0].Number.AsInt64()) mock.MeasurementBatches[1].Measurements[0].Number.AsFloat64(),
require.Equal(t, "test.gauge", 0.01)
require.Equal(t, "test.measure",
mock.MeasurementBatches[1].Measurements[0].Instrument.Name) mock.MeasurementBatches[1].Measurements[0].Instrument.Name)
require.Equal(t, map[core.Key]core.Value{ require.Equal(t, map[core.Key]core.Value{
lvals1.Key: lvals1.Value, lvals1.Key: lvals1.Value,
}, mock.MeasurementBatches[2].LabelSet.Labels) }, mock.MeasurementBatches[2].LabelSet.Labels)
require.Len(t, mock.MeasurementBatches[2].Measurements, 1) require.Len(t, mock.MeasurementBatches[2].Measurements, 1)
require.InDelta(t, float64(3), require.InDelta(t, float64(1),
mock.MeasurementBatches[2].Measurements[0].Number.AsFloat64(), mock.MeasurementBatches[2].Measurements[0].Number.AsFloat64(),
0.01) 0.01)
require.Equal(t, "test.measure", require.Equal(t, "test.observer.float",
mock.MeasurementBatches[2].Measurements[0].Instrument.Name) mock.MeasurementBatches[2].Measurements[0].Instrument.Name)
require.Equal(t, map[core.Key]core.Value{ require.Equal(t, map[core.Key]core.Value{
lvals1.Key: lvals1.Value, lvals2.Key: lvals2.Value,
}, mock.MeasurementBatches[3].LabelSet.Labels) }, mock.MeasurementBatches[3].LabelSet.Labels)
require.Len(t, mock.MeasurementBatches[3].Measurements, 1) require.Len(t, mock.MeasurementBatches[3].Measurements, 1)
require.InDelta(t, float64(1), require.InDelta(t, float64(2),
mock.MeasurementBatches[3].Measurements[0].Number.AsFloat64(), mock.MeasurementBatches[3].Measurements[0].Number.AsFloat64(),
0.01) 0.01)
require.Equal(t, "test.observer.float", require.Equal(t, "test.observer.float",
mock.MeasurementBatches[3].Measurements[0].Instrument.Name) mock.MeasurementBatches[3].Measurements[0].Instrument.Name)
require.Equal(t, map[core.Key]core.Value{ require.Equal(t, map[core.Key]core.Value{
lvals2.Key: lvals2.Value, lvals1.Key: lvals1.Value,
}, mock.MeasurementBatches[4].LabelSet.Labels) }, mock.MeasurementBatches[4].LabelSet.Labels)
require.Len(t, mock.MeasurementBatches[4].Measurements, 1) require.Len(t, mock.MeasurementBatches[4].Measurements, 1)
require.InDelta(t, float64(2), require.Equal(t, int64(1),
mock.MeasurementBatches[4].Measurements[0].Number.AsFloat64(), mock.MeasurementBatches[4].Measurements[0].Number.AsInt64())
0.01) require.Equal(t, "test.observer.int",
require.Equal(t, "test.observer.float",
mock.MeasurementBatches[4].Measurements[0].Instrument.Name) mock.MeasurementBatches[4].Measurements[0].Instrument.Name)
require.Equal(t, map[core.Key]core.Value{ require.Equal(t, map[core.Key]core.Value{
lvals1.Key: lvals1.Value, lvals2.Key: lvals2.Value,
}, mock.MeasurementBatches[5].LabelSet.Labels) }, mock.MeasurementBatches[5].LabelSet.Labels)
require.Len(t, mock.MeasurementBatches[5].Measurements, 1) require.Len(t, mock.MeasurementBatches[5].Measurements, 1)
require.Equal(t, int64(1), require.Equal(t, int64(2),
mock.MeasurementBatches[5].Measurements[0].Number.AsInt64()) mock.MeasurementBatches[5].Measurements[0].Number.AsInt64())
require.Equal(t, "test.observer.int", require.Equal(t, "test.observer.int",
mock.MeasurementBatches[5].Measurements[0].Instrument.Name) mock.MeasurementBatches[5].Measurements[0].Instrument.Name)
require.Equal(t, map[core.Key]core.Value{
lvals2.Key: lvals2.Value,
}, mock.MeasurementBatches[6].LabelSet.Labels)
require.Len(t, mock.MeasurementBatches[6].Measurements, 1)
require.Equal(t, int64(2),
mock.MeasurementBatches[6].Measurements[0].Number.AsInt64())
require.Equal(t, "test.observer.int",
mock.MeasurementBatches[6].Measurements[0].Instrument.Name)
// This tests the second Meter instance // This tests the second Meter instance
mock = sdk.Meter("test2").(*metrictest.Meter) mock = sdk.Meter("test2").(*metrictest.Meter)
require.Len(t, mock.MeasurementBatches, 1) require.Len(t, mock.MeasurementBatches, 1)
@ -158,19 +144,12 @@ func TestBound(t *testing.T) {
glob := global.MeterProvider().Meter("test") glob := global.MeterProvider().Meter("test")
lvals1 := key.String("A", "B") lvals1 := key.String("A", "B")
labels1 := glob.Labels(lvals1) labels1 := glob.Labels(lvals1)
lvals2 := key.String("C", "D")
labels2 := glob.Labels(lvals2)
counter := glob.NewFloat64Counter("test.counter") counter := glob.NewFloat64Counter("test.counter")
boundC := counter.Bind(labels1) boundC := counter.Bind(labels1)
boundC.Add(ctx, 1) boundC.Add(ctx, 1)
boundC.Add(ctx, 1) boundC.Add(ctx, 1)
gauge := glob.NewFloat64Gauge("test.gauge")
boundG := gauge.Bind(labels2)
boundG.Set(ctx, 1)
boundG.Set(ctx, 2)
measure := glob.NewInt64Measure("test.measure") measure := glob.NewInt64Measure("test.measure")
boundM := measure.Bind(labels1) boundM := measure.Bind(labels1)
boundM.Record(ctx, 1) boundM.Record(ctx, 1)
@ -180,16 +159,15 @@ func TestBound(t *testing.T) {
global.SetMeterProvider(sdk) global.SetMeterProvider(sdk)
boundC.Add(ctx, 1) boundC.Add(ctx, 1)
boundG.Set(ctx, 3)
boundM.Record(ctx, 3) boundM.Record(ctx, 3)
mock := sdk.Meter("test").(*metrictest.Meter) mock := sdk.Meter("test").(*metrictest.Meter)
require.Equal(t, 3, len(mock.MeasurementBatches)) require.Len(t, mock.MeasurementBatches, 2)
require.Equal(t, map[core.Key]core.Value{ require.Equal(t, map[core.Key]core.Value{
lvals1.Key: lvals1.Value, lvals1.Key: lvals1.Value,
}, mock.MeasurementBatches[0].LabelSet.Labels) }, mock.MeasurementBatches[0].LabelSet.Labels)
require.Equal(t, 1, len(mock.MeasurementBatches[0].Measurements)) require.Len(t, mock.MeasurementBatches[0].Measurements, 1)
require.InDelta(t, float64(1), require.InDelta(t, float64(1),
mock.MeasurementBatches[0].Measurements[0].Number.AsFloat64(), mock.MeasurementBatches[0].Measurements[0].Number.AsFloat64(),
0.01) 0.01)
@ -197,26 +175,15 @@ func TestBound(t *testing.T) {
mock.MeasurementBatches[0].Measurements[0].Instrument.Name) mock.MeasurementBatches[0].Measurements[0].Instrument.Name)
require.Equal(t, map[core.Key]core.Value{ require.Equal(t, map[core.Key]core.Value{
lvals2.Key: lvals2.Value, lvals1.Key: lvals1.Value,
}, mock.MeasurementBatches[1].LabelSet.Labels) }, mock.MeasurementBatches[1].LabelSet.Labels)
require.Equal(t, 1, len(mock.MeasurementBatches[1].Measurements)) require.Len(t, mock.MeasurementBatches[1].Measurements, 1)
require.InDelta(t, float64(3), require.Equal(t, int64(3),
mock.MeasurementBatches[1].Measurements[0].Number.AsFloat64(), mock.MeasurementBatches[1].Measurements[0].Number.AsInt64())
0.01) require.Equal(t, "test.measure",
require.Equal(t, "test.gauge",
mock.MeasurementBatches[1].Measurements[0].Instrument.Name) mock.MeasurementBatches[1].Measurements[0].Instrument.Name)
require.Equal(t, map[core.Key]core.Value{
lvals1.Key: lvals1.Value,
}, mock.MeasurementBatches[2].LabelSet.Labels)
require.Equal(t, 1, len(mock.MeasurementBatches[2].Measurements))
require.Equal(t, int64(3),
mock.MeasurementBatches[2].Measurements[0].Number.AsInt64())
require.Equal(t, "test.measure",
mock.MeasurementBatches[2].Measurements[0].Instrument.Name)
boundC.Unbind() boundC.Unbind()
boundG.Unbind()
boundM.Unbind() boundM.Unbind()
} }
@ -227,15 +194,10 @@ func TestUnbind(t *testing.T) {
glob := global.MeterProvider().Meter("test") glob := global.MeterProvider().Meter("test")
lvals1 := key.New("A").String("B") lvals1 := key.New("A").String("B")
labels1 := glob.Labels(lvals1) labels1 := glob.Labels(lvals1)
lvals2 := key.New("C").String("D")
labels2 := glob.Labels(lvals2)
counter := glob.NewFloat64Counter("test.counter") counter := glob.NewFloat64Counter("test.counter")
boundC := counter.Bind(labels1) boundC := counter.Bind(labels1)
gauge := glob.NewFloat64Gauge("test.gauge")
boundG := gauge.Bind(labels2)
measure := glob.NewInt64Measure("test.measure") measure := glob.NewInt64Measure("test.measure")
boundM := measure.Bind(labels1) boundM := measure.Bind(labels1)
@ -243,7 +205,6 @@ func TestUnbind(t *testing.T) {
observerFloat := glob.RegisterFloat64Observer("test.observer.float", nil) observerFloat := glob.RegisterFloat64Observer("test.observer.float", nil)
boundC.Unbind() boundC.Unbind()
boundG.Unbind()
boundM.Unbind() boundM.Unbind()
observerInt.Unregister() observerInt.Unregister()
observerFloat.Unregister() observerFloat.Unregister()

View File

@ -49,11 +49,11 @@ type Options struct {
// - for Counter, true implies that the metric is an up-down // - for Counter, true implies that the metric is an up-down
// Counter // Counter
// //
// - for Gauge, true implies that the metric is a
// non-descending Gauge
//
// - for Measure, true implies that the metric supports // - for Measure, true implies that the metric supports
// positive and negative values // positive and negative values
//
// - for Observer, true implies that the metric is a
// non-descending Observer
Alternate bool Alternate bool
} }
@ -65,14 +65,6 @@ type CounterOptionApplier interface {
ApplyCounterOption(*Options) ApplyCounterOption(*Options)
} }
// GaugeOptionApplier is an interface for applying metric options that
// are valid only for gauge metrics.
type GaugeOptionApplier interface {
// ApplyGaugeOption is used to make some general or
// gauge-specific changes in the Options.
ApplyGaugeOption(*Options)
}
// MeasureOptionApplier is an interface for applying metric options // MeasureOptionApplier is an interface for applying metric options
// that are valid only for measure metrics. // that are valid only for measure metrics.
type MeasureOptionApplier interface { type MeasureOptionApplier interface {
@ -122,12 +114,6 @@ type Meter interface {
// NewFloat64Counter creates a new floating point counter with // NewFloat64Counter creates a new floating point counter with
// a given name and customized with passed options. // a given name and customized with passed options.
NewFloat64Counter(name string, cos ...CounterOptionApplier) Float64Counter NewFloat64Counter(name string, cos ...CounterOptionApplier) Float64Counter
// NewInt64Gauge creates a new integral gauge with a given
// name and customized with passed options.
NewInt64Gauge(name string, gos ...GaugeOptionApplier) Int64Gauge
// NewFloat64Gauge creates a new floating point gauge with a
// given name and customized with passed options.
NewFloat64Gauge(name string, gos ...GaugeOptionApplier) Float64Gauge
// NewInt64Measure creates a new integral measure with a given // NewInt64Measure creates a new integral measure with a given
// name and customized with passed options. // name and customized with passed options.
NewInt64Measure(name string, mos ...MeasureOptionApplier) Int64Measure NewInt64Measure(name string, mos ...MeasureOptionApplier) Int64Measure
@ -187,7 +173,6 @@ type Option func(*Options)
// valid for all the kinds of metrics. // valid for all the kinds of metrics.
type OptionApplier interface { type OptionApplier interface {
CounterOptionApplier CounterOptionApplier
GaugeOptionApplier
MeasureOptionApplier MeasureOptionApplier
ObserverOptionApplier ObserverOptionApplier
// ApplyOption is used to make some general changes in the // ApplyOption is used to make some general changes in the
@ -195,12 +180,10 @@ type OptionApplier interface {
ApplyOption(*Options) ApplyOption(*Options)
} }
// CounterGaugeObserverOptionApplier is an interface for applying // CounterObserverOptionApplier is an interface for applying metric
// metric options that are valid for counter, gauge or observer // options that are valid for counter or observer metrics.
// metrics. type CounterObserverOptionApplier interface {
type CounterGaugeObserverOptionApplier interface {
CounterOptionApplier CounterOptionApplier
GaugeOptionApplier
ObserverOptionApplier ObserverOptionApplier
} }
@ -212,10 +195,6 @@ type counterOptionWrapper struct {
F Option F Option
} }
type gaugeOptionWrapper struct {
F Option
}
type measureOptionWrapper struct { type measureOptionWrapper struct {
F Option F Option
} }
@ -224,16 +203,14 @@ type observerOptionWrapper struct {
F Option F Option
} }
type counterGaugeObserverOptionWrapper struct { type counterObserverOptionWrapper struct {
FC Option FC Option
FG Option
FO Option FO Option
} }
var ( var (
_ OptionApplier = optionWrapper{} _ OptionApplier = optionWrapper{}
_ CounterOptionApplier = counterOptionWrapper{} _ CounterOptionApplier = counterOptionWrapper{}
_ GaugeOptionApplier = gaugeOptionWrapper{}
_ MeasureOptionApplier = measureOptionWrapper{} _ MeasureOptionApplier = measureOptionWrapper{}
_ ObserverOptionApplier = observerOptionWrapper{} _ ObserverOptionApplier = observerOptionWrapper{}
) )
@ -242,10 +219,6 @@ func (o optionWrapper) ApplyCounterOption(opts *Options) {
o.ApplyOption(opts) o.ApplyOption(opts)
} }
func (o optionWrapper) ApplyGaugeOption(opts *Options) {
o.ApplyOption(opts)
}
func (o optionWrapper) ApplyMeasureOption(opts *Options) { func (o optionWrapper) ApplyMeasureOption(opts *Options) {
o.ApplyOption(opts) o.ApplyOption(opts)
} }
@ -262,23 +235,15 @@ func (o counterOptionWrapper) ApplyCounterOption(opts *Options) {
o.F(opts) o.F(opts)
} }
func (o gaugeOptionWrapper) ApplyGaugeOption(opts *Options) {
o.F(opts)
}
func (o measureOptionWrapper) ApplyMeasureOption(opts *Options) { func (o measureOptionWrapper) ApplyMeasureOption(opts *Options) {
o.F(opts) o.F(opts)
} }
func (o counterGaugeObserverOptionWrapper) ApplyCounterOption(opts *Options) { func (o counterObserverOptionWrapper) ApplyCounterOption(opts *Options) {
o.FC(opts) o.FC(opts)
} }
func (o counterGaugeObserverOptionWrapper) ApplyGaugeOption(opts *Options) { func (o counterObserverOptionWrapper) ApplyObserverOption(opts *Options) {
o.FG(opts)
}
func (o counterGaugeObserverOptionWrapper) ApplyObserverOption(opts *Options) {
o.FO(opts) o.FO(opts)
} }
@ -314,16 +279,13 @@ func WithKeys(keys ...core.Key) OptionApplier {
} }
} }
// WithMonotonic sets whether a counter, a gauge or an observer is not // WithMonotonic sets whether a counter or an observer is not
// permitted to go down. // permitted to go down.
func WithMonotonic(monotonic bool) CounterGaugeObserverOptionApplier { func WithMonotonic(monotonic bool) CounterObserverOptionApplier {
return counterGaugeObserverOptionWrapper{ return counterObserverOptionWrapper{
FC: func(opts *Options) { FC: func(opts *Options) {
opts.Alternate = !monotonic opts.Alternate = !monotonic
}, },
FG: func(opts *Options) {
opts.Alternate = monotonic
},
FO: func(opts *Options) { FO: func(opts *Options) {
opts.Alternate = monotonic opts.Alternate = monotonic
}, },

View File

@ -140,117 +140,6 @@ func TestCounterOptions(t *testing.T) {
} }
} }
func TestGaugeOptions(t *testing.T) {
type testcase struct {
name string
opts []metric.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: []metric.GaugeOptionApplier{
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: "",
alt: false,
},
{
name: "description",
opts: []metric.GaugeOptionApplier{
metric.WithDescription("stuff"),
},
keys: nil,
desc: "stuff",
unit: "",
alt: false,
},
{
name: "description override",
opts: []metric.GaugeOptionApplier{
metric.WithDescription("stuff"),
metric.WithDescription("things"),
},
keys: nil,
desc: "things",
unit: "",
alt: false,
},
{
name: "unit",
opts: []metric.GaugeOptionApplier{
metric.WithUnit("s"),
},
keys: nil,
desc: "",
unit: "s",
alt: false,
},
{
name: "unit override",
opts: []metric.GaugeOptionApplier{
metric.WithUnit("s"),
metric.WithUnit("h"),
},
keys: nil,
desc: "",
unit: "h",
alt: false,
},
{
name: "monotonic",
opts: []metric.GaugeOptionApplier{
metric.WithMonotonic(true),
},
keys: nil,
desc: "",
unit: "",
alt: true,
},
{
name: "monotonic, but not really",
opts: []metric.GaugeOptionApplier{
metric.WithMonotonic(true),
metric.WithMonotonic(false),
},
keys: nil,
desc: "",
unit: "",
alt: false,
},
}
for idx, tt := range testcases {
t.Logf("Testing gauge case %s (%d)", tt.name, idx)
opts := &metric.Options{}
metric.ApplyGaugeOptions(opts, tt.opts...)
checkOptions(t, opts, &metric.Options{
Description: tt.desc,
Unit: tt.unit,
Keys: tt.keys,
Alternate: tt.alt,
})
}
}
func TestMeasureOptions(t *testing.T) { func TestMeasureOptions(t *testing.T) {
type testcase struct { type testcase struct {
name string name string
@ -506,33 +395,6 @@ func TestCounter(t *testing.T) {
} }
} }
func TestGauge(t *testing.T) {
{
meter := mock.NewMeter()
g := meter.NewFloat64Gauge("test.gauge.float")
ctx := context.Background()
labels := meter.Labels()
g.Set(ctx, 42, labels)
boundInstrument := g.Bind(labels)
boundInstrument.Set(ctx, 42)
meter.RecordBatch(ctx, labels, g.Measurement(42))
t.Log("Testing float gauge")
checkBatches(t, ctx, labels, meter, core.Float64NumberKind, g.Impl())
}
{
meter := mock.NewMeter()
g := meter.NewInt64Gauge("test.gauge.int")
ctx := context.Background()
labels := meter.Labels()
g.Set(ctx, 42, labels)
boundInstrument := g.Bind(labels)
boundInstrument.Set(ctx, 42)
meter.RecordBatch(ctx, labels, g.Measurement(42))
t.Log("Testing int gauge")
checkBatches(t, ctx, labels, meter, core.Int64NumberKind, g.Impl())
}
}
func TestMeasure(t *testing.T) { func TestMeasure(t *testing.T) {
{ {
meter := mock.NewMeter() meter := mock.NewMeter()

View File

@ -15,10 +15,9 @@
// metric package provides an API for reporting diagnostic // metric package provides an API for reporting diagnostic
// measurements using four basic kinds of instruments. // measurements using four basic kinds of instruments.
// //
// The four basic kinds are: // The three basic kinds are:
// //
// - counters // - counters
// - gauges
// - measures // - measures
// - observers // - observers
// //
@ -42,15 +41,6 @@
// function - this allows reporting negative values. To report the new // function - this allows reporting negative values. To report the new
// value, use an Add function. // value, use an Add function.
// //
// Gauges are instruments that are reporting a current state of a
// value. An example could be voltage or temperature. Gauges can be
// created with either NewFloat64Gauge or NewInt64Gauge. Gauges by
// default have no limitations about reported values - they can be
// less or greater than the last reported value. This can be changed
// with the WithMonotonic option passed to the New*Gauge function -
// this permits the reported values only to go up. To report a new
// value, use the Set function.
//
// Measures are instruments that are reporting values that are // Measures are instruments that are reporting values that are
// recorded separately to figure out some statistical properties from // recorded separately to figure out some statistical properties from
// those values (like average). An example could be temperature over // those values (like average). An example could be temperature over
@ -74,11 +64,11 @@
// callback can report multiple values. To unregister the observer, // callback can report multiple values. To unregister the observer,
// call Unregister on it. // call Unregister on it.
// //
// Counters, gauges and measures support creating bound instruments // Counters and measures support creating bound instruments for a
// for a potentially more efficient reporting. The bound instruments // potentially more efficient reporting. The bound instruments have
// have the same function names as the instruments (so a Counter bound // the same function names as the instruments (so a Counter bound
// instrument has Add, a Gauge bound instrument has Set, and a Measure // instrument has Add, and a Measure bound instrument has Record).
// bound instrument has Record). Bound Instruments can be created // Bound Instruments can be created with the Bind function of the
// with the Bind function of the respective instrument. When done with // respective instrument. When done with the bound instrument, call
// the bound instrument, call Unbind on it. // Unbind on it.
package metric // import "go.opentelemetry.io/otel/api/metric" package metric // import "go.opentelemetry.io/otel/api/metric"

View File

@ -1,113 +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 (
"context"
"go.opentelemetry.io/otel/api/core"
)
// Float64Gauge is a metric that stores the last float64 value.
type Float64Gauge struct {
commonMetric
}
// Int64Gauge is a metric that stores the last int64 value.
type Int64Gauge struct {
commonMetric
}
// BoundFloat64Gauge is a bound instrument for Float64Gauge.
//
// It inherits the Unbind function from commonBoundInstrument.
type BoundFloat64Gauge struct {
commonBoundInstrument
}
// BoundInt64Gauge is a bound instrument for Int64Gauge.
//
// It inherits the Unbind function from commonBoundInstrument.
type BoundInt64Gauge struct {
commonBoundInstrument
}
// Bind creates a bound instrument for this 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) Bind(labels LabelSet) (h BoundFloat64Gauge) {
h.commonBoundInstrument = g.bind(labels)
return
}
// Bind creates a bound instrument for this 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) Bind(labels LabelSet) (h BoundInt64Gauge) {
h.commonBoundInstrument = g.bind(labels)
return
}
// Measurement creates a Measurement object to use with batch
// recording.
func (g Float64Gauge) Measurement(value float64) Measurement {
return g.float64Measurement(value)
}
// Measurement creates a Measurement object to use with batch
// recording.
func (g Int64Gauge) Measurement(value int64) Measurement {
return g.int64Measurement(value)
}
// 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.directRecord(ctx, core.NewFloat64Number(value), labels)
}
// 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.directRecord(ctx, core.NewInt64Number(value), labels)
}
// Set assigns the passed value to the value of the gauge.
func (b BoundFloat64Gauge) Set(ctx context.Context, value float64) {
b.directRecord(ctx, core.NewFloat64Number(value))
}
// Set assigns the passed value to the value of the gauge.
func (b BoundInt64Gauge) Set(ctx context.Context, value int64) {
b.directRecord(ctx, core.NewInt64Number(value))
}

View File

@ -61,14 +61,6 @@ func (NoopMeter) NewFloat64Counter(name string, cos ...CounterOptionApplier) Flo
return WrapFloat64CounterInstrument(noopInstrument{}) return WrapFloat64CounterInstrument(noopInstrument{})
} }
func (NoopMeter) NewInt64Gauge(name string, gos ...GaugeOptionApplier) Int64Gauge {
return WrapInt64GaugeInstrument(noopInstrument{})
}
func (NoopMeter) NewFloat64Gauge(name string, gos ...GaugeOptionApplier) Float64Gauge {
return WrapFloat64GaugeInstrument(noopInstrument{})
}
func (NoopMeter) NewInt64Measure(name string, mos ...MeasureOptionApplier) Int64Measure { func (NoopMeter) NewInt64Measure(name string, mos ...MeasureOptionApplier) Int64Measure {
return WrapInt64MeasureInstrument(noopInstrument{}) return WrapInt64MeasureInstrument(noopInstrument{})
} }

View File

@ -67,22 +67,6 @@ func WrapFloat64CounterInstrument(instrument InstrumentImpl) Float64Counter {
return Float64Counter{commonMetric: newCommonMetric(instrument)} return Float64Counter{commonMetric: newCommonMetric(instrument)}
} }
// WrapInt64GaugeInstrument wraps the instrument in the type-safe
// wrapper as an integral gauge.
//
// It is mostly intended for SDKs.
func WrapInt64GaugeInstrument(instrument InstrumentImpl) Int64Gauge {
return Int64Gauge{commonMetric: newCommonMetric(instrument)}
}
// WrapFloat64GaugeInstrument wraps the instrument in the type-safe
// wrapper as an floating point gauge.
//
// It is mostly intended for SDKs.
func WrapFloat64GaugeInstrument(instrument InstrumentImpl) Float64Gauge {
return Float64Gauge{commonMetric: newCommonMetric(instrument)}
}
// WrapInt64MeasureInstrument wraps the instrument in the type-safe // WrapInt64MeasureInstrument wraps the instrument in the type-safe
// wrapper as an integral measure. // wrapper as an integral measure.
// //
@ -107,14 +91,6 @@ func ApplyCounterOptions(opts *Options, cos ...CounterOptionApplier) {
} }
} }
// ApplyGaugeOptions is a helper that applies all the gauge options to
// passed opts.
func ApplyGaugeOptions(opts *Options, gos ...GaugeOptionApplier) {
for _, o := range gos {
o.ApplyGaugeOption(opts)
}
}
// ApplyMeasureOptions is a helper that applies all the measure // ApplyMeasureOptions is a helper that applies all the measure
// options to passed opts. // options to passed opts.
func ApplyMeasureOptions(opts *Options, mos ...MeasureOptionApplier) { func ApplyMeasureOptions(opts *Options, mos ...MeasureOptionApplier) {

View File

@ -73,10 +73,16 @@ func main() {
tracer := global.TraceProvider().Tracer("ex.com/basic") tracer := global.TraceProvider().Tracer("ex.com/basic")
meter := global.MeterProvider().Meter("ex.com/basic") meter := global.MeterProvider().Meter("ex.com/basic")
oneMetric := meter.NewFloat64Gauge("ex.com.one", commonLabels := meter.Labels(lemonsKey.Int(10), key.String("A", "1"), key.String("B", "2"), key.String("C", "3"))
oneMetricCB := func(result metric.Float64ObserverResult) {
result.Observe(1, commonLabels)
}
oneMetric := meter.RegisterFloat64Observer("ex.com.one", oneMetricCB,
metric.WithKeys(fooKey, barKey, lemonsKey), metric.WithKeys(fooKey, barKey, lemonsKey),
metric.WithDescription("A gauge set to 1.0"), metric.WithDescription("An observer set to 1.0"),
) )
defer oneMetric.Unregister()
measureTwo := meter.NewFloat64Measure("ex.com.two") measureTwo := meter.NewFloat64Measure("ex.com.two")
@ -87,11 +93,6 @@ func main() {
barKey.String("bar1"), barKey.String("bar1"),
) )
commonLabels := meter.Labels(lemonsKey.Int(10), key.String("A", "1"), key.String("B", "2"), key.String("C", "3"))
gauge := oneMetric.Bind(commonLabels)
defer gauge.Unbind()
measure := measureTwo.Bind(commonLabels) measure := measureTwo.Bind(commonLabels)
defer measure.Unbind() defer measure.Unbind()
@ -101,14 +102,11 @@ func main() {
trace.SpanFromContext(ctx).SetAttributes(anotherKey.String("yes")) trace.SpanFromContext(ctx).SetAttributes(anotherKey.String("yes"))
gauge.Set(ctx, 1)
meter.RecordBatch( meter.RecordBatch(
// Note: call-site variables added as context Entries: // Note: call-site variables added as context Entries:
correlation.NewContext(ctx, anotherKey.String("xyz")), correlation.NewContext(ctx, anotherKey.String("xyz")),
commonLabels, commonLabels,
oneMetric.Measurement(1.0),
measureTwo.Measurement(2.0), measureTwo.Measurement(2.0),
) )

View File

@ -18,6 +18,7 @@ import (
"context" "context"
"log" "log"
"net/http" "net/http"
"sync"
"time" "time"
"go.opentelemetry.io/otel/api/global" "go.opentelemetry.io/otel/api/global"
@ -50,11 +51,21 @@ func main() {
defer initMeter().Stop() defer initMeter().Stop()
meter := global.MeterProvider().Meter("ex.com/basic") meter := global.MeterProvider().Meter("ex.com/basic")
observerLock := new(sync.RWMutex)
oneMetric := meter.NewFloat64Gauge("ex.com.one", observerValueToReport := new(float64)
observerLabelSetToReport := new(metric.LabelSet)
cb := func(result metric.Float64ObserverResult) {
(*observerLock).RLock()
value := *observerValueToReport
labelset := *observerLabelSetToReport
(*observerLock).RUnlock()
result.Observe(value, labelset)
}
oneMetric := meter.RegisterFloat64Observer("ex.com.one", cb,
metric.WithKeys(fooKey, barKey, lemonsKey), metric.WithKeys(fooKey, barKey, lemonsKey),
metric.WithDescription("A gauge set to 1.0"), metric.WithDescription("A measure set to 1.0"),
) )
defer oneMetric.Unregister()
measureTwo := meter.NewFloat64Measure("ex.com.two", metric.WithKeys(key.New("A"))) measureTwo := meter.NewFloat64Measure("ex.com.two", metric.WithKeys(key.New("A")))
measureThree := meter.NewFloat64Counter("ex.com.three") measureThree := meter.NewFloat64Counter("ex.com.three")
@ -64,28 +75,39 @@ func main() {
ctx := context.Background() ctx := context.Background()
(*observerLock).Lock()
*observerValueToReport = 1.0
*observerLabelSetToReport = &commonLabels
(*observerLock).Unlock()
meter.RecordBatch( meter.RecordBatch(
ctx, ctx,
commonLabels, commonLabels,
oneMetric.Measurement(1.0),
measureTwo.Measurement(2.0), measureTwo.Measurement(2.0),
measureThree.Measurement(12.0), measureThree.Measurement(12.0),
) )
time.Sleep(5 * time.Second)
(*observerLock).Lock()
*observerValueToReport = 1.0
*observerLabelSetToReport = &notSoCommonLabels
(*observerLock).Unlock()
meter.RecordBatch( meter.RecordBatch(
ctx, ctx,
notSoCommonLabels, notSoCommonLabels,
oneMetric.Measurement(1.0),
measureTwo.Measurement(2.0), measureTwo.Measurement(2.0),
measureThree.Measurement(22.0), measureThree.Measurement(22.0),
) )
time.Sleep(5 * time.Second) time.Sleep(5 * time.Second)
(*observerLock).Lock()
*observerValueToReport = 13.0
*observerLabelSetToReport = &commonLabels
(*observerLock).Unlock()
meter.RecordBatch( meter.RecordBatch(
ctx, ctx,
commonLabels, commonLabels,
oneMetric.Measurement(13.0),
measureTwo.Measurement(12.0), measureTwo.Measurement(12.0),
measureThree.Measurement(13.0), measureThree.Measurement(13.0),
) )

View File

@ -92,13 +92,13 @@ func TestBasicFormat(t *testing.T) {
for _, ao := range []adapterOutput{{ for _, ao := range []adapterOutput{{
adapter: newWithTagsAdapter(), adapter: newWithTagsAdapter(),
expected: `counter:%s|c|#A:B,C:D expected: `counter:%s|c|#A:B,C:D
gauge:%s|g|#A:B,C:D observer:%s|g|#A:B,C:D
measure:%s|h|#A:B,C:D measure:%s|h|#A:B,C:D
timer:%s|ms|#A:B,C:D timer:%s|ms|#A:B,C:D
`}, { `}, {
adapter: newNoTagsAdapter(), adapter: newNoTagsAdapter(),
expected: `counter.B.D:%s|c expected: `counter.B.D:%s|c
gauge.B.D:%s|g observer.B.D:%s|g
measure.B.D:%s|h measure.B.D:%s|h
timer.B.D:%s|ms timer.B.D:%s|ms
`}, `},
@ -126,7 +126,7 @@ timer.B.D:%s|ms
cdesc := export.NewDescriptor( cdesc := export.NewDescriptor(
"counter", export.CounterKind, nil, "", "", nkind, false) "counter", export.CounterKind, nil, "", "", nkind, false)
gdesc := export.NewDescriptor( gdesc := export.NewDescriptor(
"gauge", export.GaugeKind, nil, "", "", nkind, false) "observer", export.ObserverKind, nil, "", "", nkind, false)
mdesc := export.NewDescriptor( mdesc := export.NewDescriptor(
"measure", export.MeasureKind, nil, "", "", nkind, false) "measure", export.MeasureKind, nil, "", "", nkind, false)
tdesc := export.NewDescriptor( tdesc := export.NewDescriptor(
@ -139,7 +139,7 @@ timer.B.D:%s|ms
const value = 123.456 const value = 123.456
checkpointSet.AddCounter(cdesc, value, labels...) checkpointSet.AddCounter(cdesc, value, labels...)
checkpointSet.AddGauge(gdesc, value, labels...) checkpointSet.AddLastValue(gdesc, value, labels...)
checkpointSet.AddMeasure(mdesc, value, labels...) checkpointSet.AddMeasure(mdesc, value, labels...)
checkpointSet.AddMeasure(tdesc, value, labels...) checkpointSet.AddMeasure(tdesc, value, labels...)

View File

@ -219,20 +219,20 @@ func (c *collector) Collect(ch chan<- prometheus.Metric) {
c.exportSummary(ch, dist, numberKind, desc, labels) c.exportSummary(ch, dist, numberKind, desc, labels)
} else if sum, ok := agg.(aggregator.Sum); ok { } else if sum, ok := agg.(aggregator.Sum); ok {
c.exportCounter(ch, sum, numberKind, desc, labels) c.exportCounter(ch, sum, numberKind, desc, labels)
} else if gauge, ok := agg.(aggregator.LastValue); ok { } else if lastValue, ok := agg.(aggregator.LastValue); ok {
c.exportGauge(ch, gauge, numberKind, desc, labels) c.exportLastValue(ch, lastValue, numberKind, desc, labels)
} }
}) })
} }
func (c *collector) exportGauge(ch chan<- prometheus.Metric, gauge aggregator.LastValue, kind core.NumberKind, desc *prometheus.Desc, labels []string) { func (c *collector) exportLastValue(ch chan<- prometheus.Metric, lvagg aggregator.LastValue, kind core.NumberKind, desc *prometheus.Desc, labels []string) {
lastValue, _, err := gauge.LastValue() lv, _, err := lvagg.LastValue()
if err != nil { if err != nil {
c.exp.onError(err) c.exp.onError(err)
return return
} }
m, err := prometheus.NewConstMetric(desc, prometheus.GaugeValue, lastValue.CoerceToFloat64(kind), labels...) m, err := prometheus.NewConstMetric(desc, prometheus.GaugeValue, lv.CoerceToFloat64(kind), labels...)
if err != nil { if err != nil {
c.exp.onError(err) c.exp.onError(err)
return return

View File

@ -31,8 +31,8 @@ func TestPrometheusExporter(t *testing.T) {
counter := export.NewDescriptor( counter := export.NewDescriptor(
"counter", export.CounterKind, nil, "", "", core.Float64NumberKind, false) "counter", export.CounterKind, nil, "", "", core.Float64NumberKind, false)
gauge := export.NewDescriptor( lastValue := export.NewDescriptor(
"gauge", export.GaugeKind, nil, "", "", core.Float64NumberKind, false) "lastvalue", export.ObserverKind, nil, "", "", core.Float64NumberKind, false)
measure := export.NewDescriptor( measure := export.NewDescriptor(
"measure", export.MeasureKind, nil, "", "", core.Float64NumberKind, false) "measure", export.MeasureKind, nil, "", "", core.Float64NumberKind, false)
@ -44,8 +44,8 @@ func TestPrometheusExporter(t *testing.T) {
checkpointSet.AddCounter(counter, 15.3, labels...) checkpointSet.AddCounter(counter, 15.3, labels...)
expected = append(expected, `counter{A="B",C="D"} 15.3`) expected = append(expected, `counter{A="B",C="D"} 15.3`)
checkpointSet.AddGauge(gauge, 13.2, labels...) checkpointSet.AddLastValue(lastValue, 13.2, labels...)
expected = append(expected, `gauge{A="B",C="D"} 13.2`) expected = append(expected, `lastvalue{A="B",C="D"} 13.2`)
checkpointSet.AddMeasure(measure, 13, labels...) checkpointSet.AddMeasure(measure, 13, labels...)
checkpointSet.AddMeasure(measure, 15, labels...) checkpointSet.AddMeasure(measure, 15, labels...)
@ -64,8 +64,8 @@ func TestPrometheusExporter(t *testing.T) {
checkpointSet.AddCounter(counter, 12, missingLabels...) checkpointSet.AddCounter(counter, 12, missingLabels...)
expected = append(expected, `counter{A="E",C=""} 12`) expected = append(expected, `counter{A="E",C=""} 12`)
checkpointSet.AddGauge(gauge, 32, missingLabels...) checkpointSet.AddLastValue(lastValue, 32, missingLabels...)
expected = append(expected, `gauge{A="E",C=""} 32`) expected = append(expected, `lastvalue{A="E",C=""} 32`)
checkpointSet.AddMeasure(measure, 19, missingLabels...) checkpointSet.AddMeasure(measure, 19, missingLabels...)
expected = append(expected, `measure{A="E",C="",quantile="0.5"} 19`) expected = append(expected, `measure{A="E",C="",quantile="0.5"} 19`)

View File

@ -20,7 +20,7 @@ import (
"go.opentelemetry.io/otel/sdk/metric/aggregator/array" "go.opentelemetry.io/otel/sdk/metric/aggregator/array"
"go.opentelemetry.io/otel/sdk/metric/aggregator/counter" "go.opentelemetry.io/otel/sdk/metric/aggregator/counter"
"go.opentelemetry.io/otel/sdk/metric/aggregator/ddsketch" "go.opentelemetry.io/otel/sdk/metric/aggregator/ddsketch"
"go.opentelemetry.io/otel/sdk/metric/aggregator/gauge" "go.opentelemetry.io/otel/sdk/metric/aggregator/lastvalue"
"go.opentelemetry.io/otel/sdk/metric/aggregator/minmaxsumcount" "go.opentelemetry.io/otel/sdk/metric/aggregator/minmaxsumcount"
aggtest "go.opentelemetry.io/otel/sdk/metric/aggregator/test" aggtest "go.opentelemetry.io/otel/sdk/metric/aggregator/test"
) )
@ -82,12 +82,12 @@ func TestStdoutTimestamp(t *testing.T) {
checkpointSet := test.NewCheckpointSet(sdk.NewDefaultLabelEncoder()) checkpointSet := test.NewCheckpointSet(sdk.NewDefaultLabelEncoder())
ctx := context.Background() ctx := context.Background()
desc := export.NewDescriptor("test.name", export.GaugeKind, nil, "", "", core.Int64NumberKind, false) desc := export.NewDescriptor("test.name", export.ObserverKind, nil, "", "", core.Int64NumberKind, false)
gagg := gauge.New() lvagg := lastvalue.New()
aggtest.CheckedUpdate(t, gagg, core.NewInt64Number(321), desc) aggtest.CheckedUpdate(t, lvagg, core.NewInt64Number(321), desc)
gagg.Checkpoint(ctx, desc) lvagg.Checkpoint(ctx, desc)
checkpointSet.Add(desc, gagg) checkpointSet.Add(desc, lvagg)
if err := exporter.Export(ctx, checkpointSet); err != nil { if err := exporter.Export(ctx, checkpointSet); err != nil {
t.Fatal("Unexpected export error: ", err) t.Fatal("Unexpected export error: ", err)
@ -107,19 +107,19 @@ func TestStdoutTimestamp(t *testing.T) {
t.Fatal("JSON parse error: ", updateTS, ": ", err) t.Fatal("JSON parse error: ", updateTS, ": ", err)
} }
gaugeTS := printed["updates"].([]interface{})[0].(map[string]interface{})["time"].(string) lastValueTS := printed["updates"].([]interface{})[0].(map[string]interface{})["time"].(string)
gaugeTimestamp, err := time.Parse(time.RFC3339Nano, gaugeTS) lastValueTimestamp, err := time.Parse(time.RFC3339Nano, lastValueTS)
if err != nil { if err != nil {
t.Fatal("JSON parse error: ", gaugeTS, ": ", err) t.Fatal("JSON parse error: ", lastValueTS, ": ", err)
} }
require.True(t, updateTimestamp.After(before)) require.True(t, updateTimestamp.After(before))
require.True(t, updateTimestamp.Before(after)) require.True(t, updateTimestamp.Before(after))
require.True(t, gaugeTimestamp.After(before)) require.True(t, lastValueTimestamp.After(before))
require.True(t, gaugeTimestamp.Before(after)) require.True(t, lastValueTimestamp.Before(after))
require.True(t, gaugeTimestamp.Before(updateTimestamp)) require.True(t, lastValueTimestamp.Before(updateTimestamp))
} }
func TestStdoutCounterFormat(t *testing.T) { func TestStdoutCounterFormat(t *testing.T) {
@ -139,17 +139,17 @@ func TestStdoutCounterFormat(t *testing.T) {
require.Equal(t, `{"updates":[{"name":"test.name{A=B,C=D}","sum":123}]}`, fix.Output()) require.Equal(t, `{"updates":[{"name":"test.name{A=B,C=D}","sum":123}]}`, fix.Output())
} }
func TestStdoutGaugeFormat(t *testing.T) { func TestStdoutLastValueFormat(t *testing.T) {
fix := newFixture(t, stdout.Config{}) fix := newFixture(t, stdout.Config{})
checkpointSet := test.NewCheckpointSet(sdk.NewDefaultLabelEncoder()) checkpointSet := test.NewCheckpointSet(sdk.NewDefaultLabelEncoder())
desc := export.NewDescriptor("test.name", export.GaugeKind, nil, "", "", core.Float64NumberKind, false) desc := export.NewDescriptor("test.name", export.ObserverKind, nil, "", "", core.Float64NumberKind, false)
gagg := gauge.New() lvagg := lastvalue.New()
aggtest.CheckedUpdate(fix.t, gagg, core.NewFloat64Number(123.456), desc) aggtest.CheckedUpdate(fix.t, lvagg, core.NewFloat64Number(123.456), desc)
gagg.Checkpoint(fix.ctx, desc) lvagg.Checkpoint(fix.ctx, desc)
checkpointSet.Add(desc, gagg, key.String("A", "B"), key.String("C", "D")) checkpointSet.Add(desc, lvagg, key.String("A", "B"), key.String("C", "D"))
fix.Export(checkpointSet) fix.Export(checkpointSet)
@ -247,16 +247,16 @@ func TestStdoutEmptyDataSet(t *testing.T) {
} }
} }
func TestStdoutGaugeNotSet(t *testing.T) { func TestStdoutLastValueNotSet(t *testing.T) {
fix := newFixture(t, stdout.Config{}) fix := newFixture(t, stdout.Config{})
checkpointSet := test.NewCheckpointSet(sdk.NewDefaultLabelEncoder()) checkpointSet := test.NewCheckpointSet(sdk.NewDefaultLabelEncoder())
desc := export.NewDescriptor("test.name", export.GaugeKind, nil, "", "", core.Float64NumberKind, false) desc := export.NewDescriptor("test.name", export.ObserverKind, nil, "", "", core.Float64NumberKind, false)
gagg := gauge.New() lvagg := lastvalue.New()
gagg.Checkpoint(fix.ctx, desc) lvagg.Checkpoint(fix.ctx, desc)
checkpointSet.Add(desc, gagg, key.String("A", "B"), key.String("C", "D")) checkpointSet.Add(desc, lvagg, key.String("A", "B"), key.String("C", "D"))
fix.Export(checkpointSet) fix.Export(checkpointSet)

View File

@ -7,7 +7,7 @@ import (
export "go.opentelemetry.io/otel/sdk/export/metric" export "go.opentelemetry.io/otel/sdk/export/metric"
"go.opentelemetry.io/otel/sdk/metric/aggregator/array" "go.opentelemetry.io/otel/sdk/metric/aggregator/array"
"go.opentelemetry.io/otel/sdk/metric/aggregator/counter" "go.opentelemetry.io/otel/sdk/metric/aggregator/counter"
"go.opentelemetry.io/otel/sdk/metric/aggregator/gauge" "go.opentelemetry.io/otel/sdk/metric/aggregator/lastvalue"
) )
type CheckpointSet struct { type CheckpointSet struct {
@ -56,8 +56,8 @@ func createNumber(desc *export.Descriptor, v float64) core.Number {
return core.NewInt64Number(int64(v)) return core.NewInt64Number(int64(v))
} }
func (p *CheckpointSet) AddGauge(desc *export.Descriptor, v float64, labels ...core.KeyValue) { func (p *CheckpointSet) AddLastValue(desc *export.Descriptor, v float64, labels ...core.KeyValue) {
p.updateAggregator(desc, gauge.New(), v, labels...) p.updateAggregator(desc, lastvalue.New(), v, labels...)
} }
func (p *CheckpointSet) AddCounter(desc *export.Descriptor, v float64, labels ...core.KeyValue) { func (p *CheckpointSet) AddCounter(desc *export.Descriptor, v float64, labels ...core.KeyValue) {

View File

@ -103,7 +103,6 @@ var (
const ( const (
KindCounter Kind = iota KindCounter Kind = iota
KindGauge
KindMeasure KindMeasure
KindObserver KindObserver
) )
@ -213,27 +212,6 @@ func (m *Meter) newCounterInstrument(name string, numberKind core.NumberKind, co
} }
} }
func (m *Meter) NewInt64Gauge(name string, gos ...apimetric.GaugeOptionApplier) apimetric.Int64Gauge {
instrument := m.newGaugeInstrument(name, core.Int64NumberKind, gos...)
return apimetric.WrapInt64GaugeInstrument(instrument)
}
func (m *Meter) NewFloat64Gauge(name string, gos ...apimetric.GaugeOptionApplier) apimetric.Float64Gauge {
instrument := m.newGaugeInstrument(name, core.Float64NumberKind, gos...)
return apimetric.WrapFloat64GaugeInstrument(instrument)
}
func (m *Meter) newGaugeInstrument(name string, numberKind core.NumberKind, gos ...apimetric.GaugeOptionApplier) *Instrument {
opts := apimetric.Options{}
apimetric.ApplyGaugeOptions(&opts, gos...)
return &Instrument{
Name: name,
Kind: KindGauge,
NumberKind: numberKind,
Opts: opts,
}
}
func (m *Meter) NewInt64Measure(name string, mos ...apimetric.MeasureOptionApplier) apimetric.Int64Measure { func (m *Meter) NewInt64Measure(name string, mos ...apimetric.MeasureOptionApplier) apimetric.Int64Measure {
instrument := m.newMeasureInstrument(name, core.Int64NumberKind, mos...) instrument := m.newMeasureInstrument(name, core.Int64NumberKind, mos...)
return apimetric.WrapInt64MeasureInstrument(instrument) return apimetric.WrapInt64MeasureInstrument(instrument)

View File

@ -26,14 +26,14 @@ import (
export "go.opentelemetry.io/otel/sdk/export/metric" export "go.opentelemetry.io/otel/sdk/export/metric"
"go.opentelemetry.io/otel/sdk/export/metric/aggregator" "go.opentelemetry.io/otel/sdk/export/metric/aggregator"
"go.opentelemetry.io/otel/sdk/metric/aggregator/counter" "go.opentelemetry.io/otel/sdk/metric/aggregator/counter"
"go.opentelemetry.io/otel/sdk/metric/aggregator/gauge" "go.opentelemetry.io/otel/sdk/metric/aggregator/lastvalue"
) )
func TestInconsistentMergeErr(t *testing.T) { func TestInconsistentMergeErr(t *testing.T) {
err := aggregator.NewInconsistentMergeError(counter.New(), gauge.New()) err := aggregator.NewInconsistentMergeError(counter.New(), lastvalue.New())
require.Equal( require.Equal(
t, t,
"cannot merge *counter.Aggregator with *gauge.Aggregator: inconsistent aggregator types", "cannot merge *counter.Aggregator with *lastvalue.Aggregator: inconsistent aggregator types",
err.Error(), err.Error(),
) )
require.True(t, errors.Is(err, aggregator.ErrInconsistentType)) require.True(t, errors.Is(err, aggregator.ErrInconsistentType))
@ -67,7 +67,7 @@ func testRangeNegative(t *testing.T, alt bool, desc *export.Descriptor) {
require.Nil(t, posErr) require.Nil(t, posErr)
if desc.MetricKind() == export.GaugeKind { if desc.MetricKind() == export.ObserverKind {
require.Nil(t, negErr) require.Nil(t, negErr)
} else { } else {
require.Equal(t, negErr == nil, alt) require.Equal(t, negErr == nil, alt)
@ -79,8 +79,8 @@ func TestRangeTest(t *testing.T) {
t.Run(nkind.String(), func(t *testing.T) { t.Run(nkind.String(), func(t *testing.T) {
for _, mkind := range []export.Kind{ for _, mkind := range []export.Kind{
export.CounterKind, export.CounterKind,
export.GaugeKind,
export.MeasureKind, export.MeasureKind,
export.ObserverKind,
} { } {
t.Run(mkind.String(), func(t *testing.T) { t.Run(mkind.String(), func(t *testing.T) {
for _, alt := range []bool{true, false} { for _, alt := range []bool{true, false} {

View File

@ -9,14 +9,13 @@ func _() {
// Re-run the stringer command to generate them again. // Re-run the stringer command to generate them again.
var x [1]struct{} var x [1]struct{}
_ = x[CounterKind-0] _ = x[CounterKind-0]
_ = x[GaugeKind-1] _ = x[MeasureKind-1]
_ = x[MeasureKind-2] _ = x[ObserverKind-2]
_ = x[ObserverKind-3]
} }
const _Kind_name = "CounterKindGaugeKindMeasureKindObserverKind" const _Kind_name = "CounterKindMeasureKindObserverKind"
var _Kind_index = [...]uint8{0, 11, 20, 31, 43} var _Kind_index = [...]uint8{0, 11, 22, 34}
func (i Kind) String() string { func (i Kind) String() string {
if i < 0 || i >= Kind(len(_Kind_index)-1) { if i < 0 || i >= Kind(len(_Kind_index)-1) {

View File

@ -106,21 +106,21 @@ type AggregationSelector interface {
} }
// Aggregator implements a specific aggregation behavior, e.g., a // Aggregator implements a specific aggregation behavior, e.g., a
// behavior to track a sequence of updates to a counter, a gauge, or a // behavior to track a sequence of updates to a counter, a measure, or
// measure instrument. For the most part, counter and gauge semantics // an observer instrument. For the most part, counter semantics are
// are fixed and the provided implementations should be used. Measure // fixed and the provided implementation should be used. Measure and
// metrics offer a wide range of potential tradeoffs and several // observer metrics offer a wide range of potential tradeoffs and
// implementations are provided. // several implementations are provided.
// //
// Aggregators are meant to compute the change (i.e., delta) in state // Aggregators are meant to compute the change (i.e., delta) in state
// from one checkpoint to the next, with the exception of gauge // from one checkpoint to the next, with the exception of LastValue
// aggregators. Gauge aggregators are required to maintain the last // aggregators. LastValue aggregators are required to maintain the last
// value across checkpoints to implement montonic gauge support. // value across checkpoints.
// //
// Note that any Aggregator may be attached to any instrument--this is // Note that any Aggregator may be attached to any instrument--this is
// the result of the OpenTelemetry API/SDK separation. It is possible // the result of the OpenTelemetry API/SDK separation. It is possible
// to attach a counter aggregator to a measure instrument (to compute // to attach a counter aggregator to a Measure instrument (to compute
// a simple sum) or a gauge instrument to a measure instrument (to // a simple sum) or a LastValue aggregator to a measure instrument (to
// compute the last value). // compute the last value).
type Aggregator interface { type Aggregator interface {
// Update receives a new measured value and incorporates it // Update receives a new measured value and incorporates it
@ -290,9 +290,6 @@ const (
// Counter kind indicates a counter instrument. // Counter kind indicates a counter instrument.
CounterKind Kind = iota CounterKind Kind = iota
// Gauge kind indicates a gauge instrument.
GaugeKind
// Measure kind indicates a measure instrument. // Measure kind indicates a measure instrument.
MeasureKind MeasureKind
@ -346,8 +343,8 @@ func (d *Descriptor) Name() string {
return d.name return d.name
} }
// MetricKind returns the kind of instrument: counter, gauge, or // MetricKind returns the kind of instrument: counter, measure, or
// measure. // observer.
func (d *Descriptor) MetricKind() Kind { func (d *Descriptor) MetricKind() Kind {
return d.metricKind return d.metricKind
} }
@ -381,8 +378,8 @@ func (d *Descriptor) NumberKind() core.NumberKind {
// instrument was selected. It returns true if: // instrument was selected. It returns true if:
// //
// - A counter instrument is non-monotonic // - A counter instrument is non-monotonic
// - A gauge instrument is monotonic
// - A measure instrument is non-absolute // - A measure instrument is non-absolute
// - An observer instrument is monotonic
// //
// TODO: Consider renaming this method, or expanding to provide // TODO: Consider renaming this method, or expanding to provide
// kind-specific tests (e.g., Monotonic(), Absolute()). // kind-specific tests (e.g., Monotonic(), Absolute()).

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package gauge // import "go.opentelemetry.io/otel/sdk/metric/aggregator/gauge" package lastvalue // import "go.opentelemetry.io/otel/sdk/metric/aggregator/lastvalue"
import ( import (
"context" "context"
@ -25,25 +25,20 @@ import (
"go.opentelemetry.io/otel/sdk/export/metric/aggregator" "go.opentelemetry.io/otel/sdk/export/metric/aggregator"
) )
// 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 ( type (
// Aggregator aggregates gauge events. // Aggregator aggregates lastValue events.
Aggregator struct { Aggregator struct {
// current is an atomic pointer to *gaugeData. It is never nil. // current is an atomic pointer to *lastValueData. It is never nil.
current unsafe.Pointer current unsafe.Pointer
// checkpoint is a copy of the current value taken in Checkpoint() // checkpoint is a copy of the current value taken in Checkpoint()
checkpoint unsafe.Pointer checkpoint unsafe.Pointer
} }
// gaugeData stores the current value of a gauge along with // lastValueData stores the current value of a lastValue along with
// a sequence number to determine the winner of a race. // a sequence number to determine the winner of a race.
gaugeData struct { lastValueData struct {
// value is the int64- or float64-encoded Set() data // value is the int64- or float64-encoded Set() data
// //
// value needs to be aligned for 64-bit atomic operations. // value needs to be aligned for 64-bit atomic operations.
@ -51,7 +46,7 @@ type (
// timestamp indicates when this record was submitted. // timestamp indicates when this record was submitted.
// this can be used to pick a winner when multiple // this can be used to pick a winner when multiple
// records contain gauge data for the same labels due // records contain lastValue data for the same labels due
// to races. // to races.
timestamp time.Time timestamp time.Time
} }
@ -60,25 +55,25 @@ type (
var _ export.Aggregator = &Aggregator{} var _ export.Aggregator = &Aggregator{}
var _ aggregator.LastValue = &Aggregator{} var _ aggregator.LastValue = &Aggregator{}
// An unset gauge has zero timestamp and zero value. // An unset lastValue has zero timestamp and zero value.
var unsetGauge = &gaugeData{} var unsetLastValue = &lastValueData{}
// New returns a new gauge aggregator. This aggregator retains the // New returns a new lastValue aggregator. This aggregator retains the
// last value and timestamp that were recorded. // last value and timestamp that were recorded.
func New() *Aggregator { func New() *Aggregator {
return &Aggregator{ return &Aggregator{
current: unsafe.Pointer(unsetGauge), current: unsafe.Pointer(unsetLastValue),
checkpoint: unsafe.Pointer(unsetGauge), checkpoint: unsafe.Pointer(unsetLastValue),
} }
} }
// LastValue returns the last-recorded gauge value and the // LastValue returns the last-recorded lastValue value and the
// corresponding timestamp. The error value aggregator.ErrNoLastValue // corresponding timestamp. The error value aggregator.ErrNoLastValue
// will be returned if (due to a race condition) the checkpoint was // will be returned if (due to a race condition) the checkpoint was
// computed before the first value was set. // computed before the first value was set.
func (g *Aggregator) LastValue() (core.Number, time.Time, error) { func (g *Aggregator) LastValue() (core.Number, time.Time, error) {
gd := (*gaugeData)(g.checkpoint) gd := (*lastValueData)(g.checkpoint)
if gd == unsetGauge { if gd == unsetLastValue {
return core.Number(0), time.Time{}, aggregator.ErrNoLastValue return core.Number(0), time.Time{}, aggregator.ErrNoLastValue
} }
return gd.value.AsNumber(), gd.timestamp, nil return gd.value.AsNumber(), gd.timestamp, nil
@ -91,68 +86,25 @@ func (g *Aggregator) Checkpoint(ctx context.Context, _ *export.Descriptor) {
// Update atomically sets the current "last" value. // Update atomically sets the current "last" value.
func (g *Aggregator) Update(_ context.Context, number core.Number, desc *export.Descriptor) error { func (g *Aggregator) Update(_ context.Context, number core.Number, desc *export.Descriptor) error {
if !desc.Alternate() { ngd := &lastValueData{
g.updateNonMonotonic(number)
return nil
}
return g.updateMonotonic(number, desc)
}
func (g *Aggregator) updateNonMonotonic(number core.Number) {
ngd := &gaugeData{
value: number, value: number,
timestamp: time.Now(), timestamp: time.Now(),
} }
atomic.StorePointer(&g.current, unsafe.Pointer(ngd)) atomic.StorePointer(&g.current, unsafe.Pointer(ngd))
return nil
} }
func (g *Aggregator) updateMonotonic(number core.Number, desc *export.Descriptor) error { // Merge combines state from two aggregators. The most-recently set
ngd := &gaugeData{ // value is chosen.
timestamp: time.Now(),
value: number,
}
kind := desc.NumberKind()
for {
gd := (*gaugeData)(atomic.LoadPointer(&g.current))
if gd.value.CompareNumber(kind, number) > 0 {
return aggregator.ErrNonMonotoneInput
}
if atomic.CompareAndSwapPointer(&g.current, unsafe.Pointer(gd), unsafe.Pointer(ngd)) {
return nil
}
}
}
// Merge combines state from two aggregators. If the gauge is
// declared as monotonic, the greater value is chosen. If the gauge
// is declared as non-monotonic, the most-recently set value is
// chosen.
func (g *Aggregator) Merge(oa export.Aggregator, desc *export.Descriptor) error { func (g *Aggregator) Merge(oa export.Aggregator, desc *export.Descriptor) error {
o, _ := oa.(*Aggregator) o, _ := oa.(*Aggregator)
if o == nil { if o == nil {
return aggregator.NewInconsistentMergeError(g, oa) return aggregator.NewInconsistentMergeError(g, oa)
} }
ggd := (*gaugeData)(atomic.LoadPointer(&g.checkpoint)) ggd := (*lastValueData)(atomic.LoadPointer(&g.checkpoint))
ogd := (*gaugeData)(atomic.LoadPointer(&o.checkpoint)) ogd := (*lastValueData)(atomic.LoadPointer(&o.checkpoint))
if desc.Alternate() {
// Monotonic: use the greater value
cmp := ggd.value.CompareNumber(desc.NumberKind(), ogd.value)
if cmp > 0 {
return nil
}
if cmp < 0 {
g.checkpoint = unsafe.Pointer(ogd)
return nil
}
}
// Non-monotonic gauge or equal values
if ggd.timestamp.After(ogd.timestamp) { if ggd.timestamp.After(ogd.timestamp) {
return nil return nil
} }

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package gauge package lastvalue
import ( import (
"context" "context"
@ -38,8 +38,8 @@ var _ export.Aggregator = &Aggregator{}
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
fields := []ottest.FieldOffset{ fields := []ottest.FieldOffset{
{ {
Name: "gaugeData.value", Name: "lastValueData.value",
Offset: unsafe.Offsetof(gaugeData{}.value), Offset: unsafe.Offsetof(lastValueData{}.value),
}, },
} }
if !ottest.Aligned8Byte(fields, os.Stderr) { if !ottest.Aligned8Byte(fields, os.Stderr) {
@ -49,13 +49,13 @@ func TestMain(m *testing.M) {
os.Exit(m.Run()) os.Exit(m.Run())
} }
func TestGaugeNonMonotonic(t *testing.T) { func TestLastValueUpdate(t *testing.T) {
ctx := context.Background() ctx := context.Background()
test.RunProfiles(t, func(t *testing.T, profile test.Profile) { test.RunProfiles(t, func(t *testing.T, profile test.Profile) {
agg := New() agg := New()
record := test.NewAggregatorTest(export.GaugeKind, profile.NumberKind, false) record := test.NewAggregatorTest(export.ObserverKind, profile.NumberKind, false)
var last core.Number var last core.Number
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
@ -72,66 +72,14 @@ func TestGaugeNonMonotonic(t *testing.T) {
}) })
} }
func TestGaugeMonotonic(t *testing.T) { func TestLastValueMerge(t *testing.T) {
ctx := context.Background()
test.RunProfiles(t, func(t *testing.T, profile test.Profile) {
agg := New()
record := test.NewAggregatorTest(export.GaugeKind, profile.NumberKind, true)
small := profile.Random(+1)
last := small
for i := 0; i < count; i++ {
x := profile.Random(+1)
last.AddNumber(profile.NumberKind, x)
test.CheckedUpdate(t, agg, last, record)
}
agg.Checkpoint(ctx, record)
lv, _, err := agg.LastValue()
require.Equal(t, last, lv, "Same last value - monotonic")
require.Nil(t, err)
})
}
func TestGaugeMonotonicDescending(t *testing.T) {
ctx := context.Background()
test.RunProfiles(t, func(t *testing.T, profile test.Profile) {
agg := New()
record := test.NewAggregatorTest(export.GaugeKind, profile.NumberKind, true)
first := profile.Random(+1)
test.CheckedUpdate(t, agg, first, record)
for i := 0; i < count; i++ {
x := profile.Random(-1)
err := agg.Update(ctx, x, record)
if err != aggregator.ErrNonMonotoneInput {
t.Error("Expected ErrNonMonotoneInput", err)
}
}
agg.Checkpoint(ctx, record)
lv, _, err := agg.LastValue()
require.Equal(t, first, lv, "Same last value - monotonic")
require.Nil(t, err)
})
}
func TestGaugeNormalMerge(t *testing.T) {
ctx := context.Background() ctx := context.Background()
test.RunProfiles(t, func(t *testing.T, profile test.Profile) { test.RunProfiles(t, func(t *testing.T, profile test.Profile) {
agg1 := New() agg1 := New()
agg2 := New() agg2 := New()
descriptor := test.NewAggregatorTest(export.GaugeKind, profile.NumberKind, false) descriptor := test.NewAggregatorTest(export.ObserverKind, profile.NumberKind, false)
first1 := profile.Random(+1) first1 := profile.Random(+1)
first2 := profile.Random(+1) first2 := profile.Random(+1)
@ -158,39 +106,8 @@ func TestGaugeNormalMerge(t *testing.T) {
}) })
} }
func TestGaugeMonotonicMerge(t *testing.T) { func TestLastValueNotSet(t *testing.T) {
ctx := context.Background() descriptor := test.NewAggregatorTest(export.ObserverKind, core.Int64NumberKind, true)
test.RunProfiles(t, func(t *testing.T, profile test.Profile) {
agg1 := New()
agg2 := New()
descriptor := test.NewAggregatorTest(export.GaugeKind, profile.NumberKind, true)
first1 := profile.Random(+1)
test.CheckedUpdate(t, agg1, first1, descriptor)
first2 := profile.Random(+1)
first2.AddNumber(profile.NumberKind, first1)
test.CheckedUpdate(t, agg2, first2, descriptor)
agg1.Checkpoint(ctx, descriptor)
agg2.Checkpoint(ctx, descriptor)
test.CheckedMerge(t, agg1, agg2, descriptor)
_, ts2, err := agg1.LastValue()
require.Nil(t, err)
lv, ts1, err := agg1.LastValue()
require.Nil(t, err)
require.Equal(t, first2, lv, "Merged value - monotonic")
require.Equal(t, ts2, ts1, "Merged timestamp - monotonic")
})
}
func TestGaugeNotSet(t *testing.T) {
descriptor := test.NewAggregatorTest(export.GaugeKind, core.Int64NumberKind, true)
g := New() g := New()
g.Checkpoint(context.Background(), descriptor) g.Checkpoint(context.Background(), descriptor)

View File

@ -30,13 +30,13 @@ func TestGroupingStateless(t *testing.T) {
ctx := context.Background() ctx := context.Background()
b := defaultkeys.New(test.NewAggregationSelector(), test.GroupEncoder, false) b := defaultkeys.New(test.NewAggregationSelector(), test.GroupEncoder, false)
_ = b.Process(ctx, test.NewGaugeRecord(test.GaugeADesc, test.Labels1, 10)) _ = b.Process(ctx, test.NewLastValueRecord(test.LastValueADesc, test.Labels1, 10))
_ = b.Process(ctx, test.NewGaugeRecord(test.GaugeADesc, test.Labels2, 20)) _ = b.Process(ctx, test.NewLastValueRecord(test.LastValueADesc, test.Labels2, 20))
_ = b.Process(ctx, test.NewGaugeRecord(test.GaugeADesc, test.Labels3, 30)) _ = b.Process(ctx, test.NewLastValueRecord(test.LastValueADesc, test.Labels3, 30))
_ = b.Process(ctx, test.NewGaugeRecord(test.GaugeBDesc, test.Labels1, 10)) _ = b.Process(ctx, test.NewLastValueRecord(test.LastValueBDesc, test.Labels1, 10))
_ = b.Process(ctx, test.NewGaugeRecord(test.GaugeBDesc, test.Labels2, 20)) _ = b.Process(ctx, test.NewLastValueRecord(test.LastValueBDesc, test.Labels2, 20))
_ = b.Process(ctx, test.NewGaugeRecord(test.GaugeBDesc, test.Labels3, 30)) _ = b.Process(ctx, test.NewLastValueRecord(test.LastValueBDesc, test.Labels3, 30))
_ = b.Process(ctx, test.NewCounterRecord(test.CounterADesc, test.Labels1, 10)) _ = b.Process(ctx, test.NewCounterRecord(test.CounterADesc, test.Labels1, 10))
_ = b.Process(ctx, test.NewCounterRecord(test.CounterADesc, test.Labels2, 20)) _ = b.Process(ctx, test.NewCounterRecord(test.CounterADesc, test.Labels2, 20))
@ -52,18 +52,18 @@ func TestGroupingStateless(t *testing.T) {
records := test.Output{} records := test.Output{}
checkpointSet.ForEach(records.AddTo) checkpointSet.ForEach(records.AddTo)
// Repeat for {counter,gauge}.{1,2}. // Repeat for {counter,lastvalue}.{1,2}.
// Output gauge should have only the "G=H" and "G=" keys. // Output lastvalue should have only the "G=H" and "G=" keys.
// Output counter should have only the "C=D" and "C=" keys. // Output counter should have only the "C=D" and "C=" keys.
require.EqualValues(t, map[string]int64{ require.EqualValues(t, map[string]int64{
"counter.a/C=D": 30, // labels1 + labels2 "counter.a/C=D": 30, // labels1 + labels2
"counter.a/C=": 40, // labels3 "counter.a/C=": 40, // labels3
"counter.b/C=D": 30, // labels1 + labels2 "counter.b/C=D": 30, // labels1 + labels2
"counter.b/C=": 40, // labels3 "counter.b/C=": 40, // labels3
"gauge.a/G=H": 10, // labels1 "lastvalue.a/G=H": 10, // labels1
"gauge.a/G=": 30, // labels3 = last value "lastvalue.a/G=": 30, // labels3 = last value
"gauge.b/G=H": 10, // labels1 "lastvalue.b/G=H": 10, // labels1
"gauge.b/G=": 30, // labels3 = last value "lastvalue.b/G=": 30, // labels3 = last value
}, records) }, records)
// Verify that state is reset by FinishedCollection() // Verify that state is reset by FinishedCollection()

View File

@ -24,7 +24,7 @@ import (
export "go.opentelemetry.io/otel/sdk/export/metric" export "go.opentelemetry.io/otel/sdk/export/metric"
sdk "go.opentelemetry.io/otel/sdk/metric" sdk "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/metric/aggregator/counter" "go.opentelemetry.io/otel/sdk/metric/aggregator/counter"
"go.opentelemetry.io/otel/sdk/metric/aggregator/gauge" "go.opentelemetry.io/otel/sdk/metric/aggregator/lastvalue"
) )
type ( type (
@ -41,11 +41,11 @@ type (
) )
var ( var (
// GaugeADesc and GaugeBDesc group by "G" // LastValueADesc and LastValueBDesc group by "G"
GaugeADesc = export.NewDescriptor( LastValueADesc = export.NewDescriptor(
"gauge.a", export.GaugeKind, []core.Key{key.New("G")}, "", "", core.Int64NumberKind, false) "lastvalue.a", export.ObserverKind, []core.Key{key.New("G")}, "", "", core.Int64NumberKind, false)
GaugeBDesc = export.NewDescriptor( LastValueBDesc = export.NewDescriptor(
"gauge.b", export.GaugeKind, []core.Key{key.New("G")}, "", "", core.Int64NumberKind, false) "lastvalue.b", export.ObserverKind, []core.Key{key.New("G")}, "", "", core.Int64NumberKind, false)
// CounterADesc and CounterBDesc group by "C" // CounterADesc and CounterBDesc group by "C"
CounterADesc = export.NewDescriptor( CounterADesc = export.NewDescriptor(
"counter.a", export.CounterKind, []core.Key{key.New("C")}, "", "", core.Int64NumberKind, false) "counter.a", export.CounterKind, []core.Key{key.New("C")}, "", "", core.Int64NumberKind, false)
@ -57,7 +57,7 @@ var (
// GroupEncoder uses the SDK default encoder // GroupEncoder uses the SDK default encoder
GroupEncoder = sdk.NewDefaultLabelEncoder() GroupEncoder = sdk.NewDefaultLabelEncoder()
// Gauge groups are (labels1), (labels2+labels3) // LastValue groups are (labels1), (labels2+labels3)
// Counter groups are (labels1+labels2), (labels3) // Counter groups are (labels1+labels2), (labels3)
// Labels1 has G=H and C=D // Labels1 has G=H and C=D
@ -70,7 +70,7 @@ var (
// NewAggregationSelector returns a policy that is consistent with the // NewAggregationSelector returns a policy that is consistent with the
// test descriptors above. I.e., it returns counter.New() for counter // test descriptors above. I.e., it returns counter.New() for counter
// instruments and gauge.New for gauge instruments. // instruments and lastvalue.New for lastValue instruments.
func NewAggregationSelector() export.AggregationSelector { func NewAggregationSelector() export.AggregationSelector {
return &testAggregationSelector{} return &testAggregationSelector{}
} }
@ -79,8 +79,8 @@ func (*testAggregationSelector) AggregatorFor(desc *export.Descriptor) export.Ag
switch desc.MetricKind() { switch desc.MetricKind() {
case export.CounterKind: case export.CounterKind:
return counter.New() return counter.New()
case export.GaugeKind: case export.ObserverKind:
return gauge.New() return lastvalue.New()
default: default:
panic("Invalid descriptor MetricKind for this test") panic("Invalid descriptor MetricKind for this test")
} }
@ -104,18 +104,18 @@ func (Encoder) Encode(labels []core.KeyValue) string {
return sb.String() return sb.String()
} }
// GaugeAgg returns a checkpointed gauge aggregator w/ the specified descriptor and value. // LastValueAgg returns a checkpointed lastValue aggregator w/ the specified descriptor and value.
func GaugeAgg(desc *export.Descriptor, v int64) export.Aggregator { func LastValueAgg(desc *export.Descriptor, v int64) export.Aggregator {
ctx := context.Background() ctx := context.Background()
gagg := gauge.New() gagg := lastvalue.New()
_ = gagg.Update(ctx, core.NewInt64Number(v), desc) _ = gagg.Update(ctx, core.NewInt64Number(v), desc)
gagg.Checkpoint(ctx, desc) gagg.Checkpoint(ctx, desc)
return gagg return gagg
} }
// Convenience method for building a test exported gauge record. // Convenience method for building a test exported lastValue record.
func NewGaugeRecord(desc *export.Descriptor, labels export.Labels, value int64) export.Record { func NewLastValueRecord(desc *export.Descriptor, labels export.Labels, value int64) export.Record {
return export.NewRecord(desc, labels, GaugeAgg(desc, value)) return export.NewRecord(desc, labels, LastValueAgg(desc, value))
} }
// Convenience method for building a test exported counter record. // Convenience method for building a test exported counter record.
@ -132,7 +132,7 @@ func CounterAgg(desc *export.Descriptor, v int64) export.Aggregator {
return cagg return cagg
} }
// AddTo adds a name/label-encoding entry with the gauge or counter // AddTo adds a name/label-encoding entry with the lastValue or counter
// value to the output map. // value to the output map.
func (o Output) AddTo(rec export.Record) { func (o Output) AddTo(rec export.Record) {
labels := rec.Labels() labels := rec.Labels()
@ -142,7 +142,7 @@ func (o Output) AddTo(rec export.Record) {
case *counter.Aggregator: case *counter.Aggregator:
sum, _ := t.Sum() sum, _ := t.Sum()
value = sum.AsInt64() value = sum.AsInt64()
case *gauge.Aggregator: case *lastvalue.Aggregator:
lv, _, _ := t.LastValue() lv, _, _ := t.LastValue()
value = lv.AsInt64() value = lv.AsInt64()
} }

View File

@ -32,18 +32,18 @@ func TestUngroupedStateless(t *testing.T) {
ctx := context.Background() ctx := context.Background()
b := ungrouped.New(test.NewAggregationSelector(), false) b := ungrouped.New(test.NewAggregationSelector(), false)
// Set initial gauge values // Set initial lastValue values
_ = b.Process(ctx, test.NewGaugeRecord(test.GaugeADesc, test.Labels1, 10)) _ = b.Process(ctx, test.NewLastValueRecord(test.LastValueADesc, test.Labels1, 10))
_ = b.Process(ctx, test.NewGaugeRecord(test.GaugeADesc, test.Labels2, 20)) _ = b.Process(ctx, test.NewLastValueRecord(test.LastValueADesc, test.Labels2, 20))
_ = b.Process(ctx, test.NewGaugeRecord(test.GaugeADesc, test.Labels3, 30)) _ = b.Process(ctx, test.NewLastValueRecord(test.LastValueADesc, test.Labels3, 30))
_ = b.Process(ctx, test.NewGaugeRecord(test.GaugeBDesc, test.Labels1, 10)) _ = b.Process(ctx, test.NewLastValueRecord(test.LastValueBDesc, test.Labels1, 10))
_ = b.Process(ctx, test.NewGaugeRecord(test.GaugeBDesc, test.Labels2, 20)) _ = b.Process(ctx, test.NewLastValueRecord(test.LastValueBDesc, test.Labels2, 20))
_ = b.Process(ctx, test.NewGaugeRecord(test.GaugeBDesc, test.Labels3, 30)) _ = b.Process(ctx, test.NewLastValueRecord(test.LastValueBDesc, test.Labels3, 30))
// Another gauge Set for Labels1 // Another lastValue Set for Labels1
_ = b.Process(ctx, test.NewGaugeRecord(test.GaugeADesc, test.Labels1, 50)) _ = b.Process(ctx, test.NewLastValueRecord(test.LastValueADesc, test.Labels1, 50))
_ = b.Process(ctx, test.NewGaugeRecord(test.GaugeBDesc, test.Labels1, 50)) _ = b.Process(ctx, test.NewLastValueRecord(test.LastValueBDesc, test.Labels1, 50))
// Set initial counter values // Set initial counter values
_ = b.Process(ctx, test.NewCounterRecord(test.CounterADesc, test.Labels1, 10)) _ = b.Process(ctx, test.NewCounterRecord(test.CounterADesc, test.Labels1, 10))
@ -64,21 +64,21 @@ func TestUngroupedStateless(t *testing.T) {
records := test.Output{} records := test.Output{}
checkpointSet.ForEach(records.AddTo) checkpointSet.ForEach(records.AddTo)
// Output gauge should have only the "G=H" and "G=" keys. // Output lastvalue should have only the "G=H" and "G=" keys.
// Output counter should have only the "C=D" and "C=" keys. // Output counter should have only the "C=D" and "C=" keys.
require.EqualValues(t, map[string]int64{ require.EqualValues(t, map[string]int64{
"counter.a/G~H&C~D": 60, // labels1 "counter.a/G~H&C~D": 60, // labels1
"counter.a/C~D&E~F": 20, // labels2 "counter.a/C~D&E~F": 20, // labels2
"counter.a/": 40, // labels3 "counter.a/": 40, // labels3
"counter.b/G~H&C~D": 60, // labels1 "counter.b/G~H&C~D": 60, // labels1
"counter.b/C~D&E~F": 20, // labels2 "counter.b/C~D&E~F": 20, // labels2
"counter.b/": 40, // labels3 "counter.b/": 40, // labels3
"gauge.a/G~H&C~D": 50, // labels1 "lastvalue.a/G~H&C~D": 50, // labels1
"gauge.a/C~D&E~F": 20, // labels2 "lastvalue.a/C~D&E~F": 20, // labels2
"gauge.a/": 30, // labels3 "lastvalue.a/": 30, // labels3
"gauge.b/G~H&C~D": 50, // labels1 "lastvalue.b/G~H&C~D": 50, // labels1
"gauge.b/C~D&E~F": 20, // labels2 "lastvalue.b/C~D&E~F": 20, // labels2
"gauge.b/": 30, // labels3 "lastvalue.b/": 30, // labels3
}, records) }, records)
// Verify that state was reset // Verify that state was reset

View File

@ -28,7 +28,7 @@ import (
sdk "go.opentelemetry.io/otel/sdk/metric" sdk "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/metric/aggregator/counter" "go.opentelemetry.io/otel/sdk/metric/aggregator/counter"
"go.opentelemetry.io/otel/sdk/metric/aggregator/ddsketch" "go.opentelemetry.io/otel/sdk/metric/aggregator/ddsketch"
"go.opentelemetry.io/otel/sdk/metric/aggregator/gauge" "go.opentelemetry.io/otel/sdk/metric/aggregator/lastvalue"
"go.opentelemetry.io/otel/sdk/metric/aggregator/minmaxsumcount" "go.opentelemetry.io/otel/sdk/metric/aggregator/minmaxsumcount"
) )
@ -47,14 +47,13 @@ func newFixture(b *testing.B) *benchFixture {
} }
func (*benchFixture) AggregatorFor(descriptor *export.Descriptor) export.Aggregator { func (*benchFixture) AggregatorFor(descriptor *export.Descriptor) export.Aggregator {
switch descriptor.MetricKind() { name := descriptor.Name()
case export.CounterKind: switch {
case strings.HasSuffix(name, "counter"):
return counter.New() return counter.New()
case export.GaugeKind: case strings.HasSuffix(name, "lastvalue"):
return gauge.New() return lastvalue.New()
case export.ObserverKind: default:
fallthrough
case export.MeasureKind:
if strings.HasSuffix(descriptor.Name(), "minmaxsumcount") { if strings.HasSuffix(descriptor.Name(), "minmaxsumcount") {
return minmaxsumcount.New(descriptor) return minmaxsumcount.New(descriptor)
} else if strings.HasSuffix(descriptor.Name(), "ddsketch") { } else if strings.HasSuffix(descriptor.Name(), "ddsketch") {
@ -247,59 +246,59 @@ func BenchmarkFloat64CounterHandleAdd(b *testing.B) {
} }
} }
// Gauges // LastValue
func BenchmarkInt64GaugeAdd(b *testing.B) { func BenchmarkInt64LastValueAdd(b *testing.B) {
ctx := context.Background() ctx := context.Background()
fix := newFixture(b) fix := newFixture(b)
labs := fix.sdk.Labels(makeLabels(1)...) labs := fix.sdk.Labels(makeLabels(1)...)
gau := fix.sdk.NewInt64Gauge("int64.gauge") mea := fix.sdk.NewInt64Measure("int64.lastvalue")
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
gau.Set(ctx, int64(i), labs) mea.Record(ctx, int64(i), labs)
} }
} }
func BenchmarkInt64GaugeHandleAdd(b *testing.B) { func BenchmarkInt64LastValueHandleAdd(b *testing.B) {
ctx := context.Background() ctx := context.Background()
fix := newFixture(b) fix := newFixture(b)
labs := fix.sdk.Labels(makeLabels(1)...) labs := fix.sdk.Labels(makeLabels(1)...)
gau := fix.sdk.NewInt64Gauge("int64.gauge") mea := fix.sdk.NewInt64Measure("int64.lastvalue")
handle := gau.Bind(labs) handle := mea.Bind(labs)
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
handle.Set(ctx, int64(i)) handle.Record(ctx, int64(i))
} }
} }
func BenchmarkFloat64GaugeAdd(b *testing.B) { func BenchmarkFloat64LastValueAdd(b *testing.B) {
ctx := context.Background() ctx := context.Background()
fix := newFixture(b) fix := newFixture(b)
labs := fix.sdk.Labels(makeLabels(1)...) labs := fix.sdk.Labels(makeLabels(1)...)
gau := fix.sdk.NewFloat64Gauge("float64.gauge") mea := fix.sdk.NewFloat64Measure("float64.lastvalue")
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
gau.Set(ctx, float64(i), labs) mea.Record(ctx, float64(i), labs)
} }
} }
func BenchmarkFloat64GaugeHandleAdd(b *testing.B) { func BenchmarkFloat64LastValueHandleAdd(b *testing.B) {
ctx := context.Background() ctx := context.Background()
fix := newFixture(b) fix := newFixture(b)
labs := fix.sdk.Labels(makeLabels(1)...) labs := fix.sdk.Labels(makeLabels(1)...)
gau := fix.sdk.NewFloat64Gauge("float64.gauge") mea := fix.sdk.NewFloat64Measure("float64.lastvalue")
handle := gau.Bind(labs) handle := mea.Bind(labs)
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
handle.Set(ctx, float64(i)) handle.Record(ctx, float64(i))
} }
} }

View File

@ -30,7 +30,7 @@ import (
sdk "go.opentelemetry.io/otel/sdk/metric" sdk "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/metric/aggregator/array" "go.opentelemetry.io/otel/sdk/metric/aggregator/array"
"go.opentelemetry.io/otel/sdk/metric/aggregator/counter" "go.opentelemetry.io/otel/sdk/metric/aggregator/counter"
"go.opentelemetry.io/otel/sdk/metric/aggregator/gauge" "go.opentelemetry.io/otel/sdk/metric/aggregator/lastvalue"
) )
type correctnessBatcher struct { type correctnessBatcher struct {
@ -152,7 +152,7 @@ func TestRecordNaN(t *testing.T) {
ctx := context.Background() ctx := context.Background()
batcher := &correctnessBatcher{ batcher := &correctnessBatcher{
t: t, t: t,
agg: gauge.New(), agg: lastvalue.New(),
} }
sdk := sdk.New(batcher, sdk.NewDefaultLabelEncoder()) sdk := sdk.New(batcher, sdk.NewDefaultLabelEncoder())
@ -160,10 +160,10 @@ func TestRecordNaN(t *testing.T) {
sdk.SetErrorHandler(func(handleErr error) { sdk.SetErrorHandler(func(handleErr error) {
sdkErr = handleErr sdkErr = handleErr
}) })
g := sdk.NewFloat64Gauge("gauge.name") c := sdk.NewFloat64Counter("counter.name")
require.Nil(t, sdkErr) require.Nil(t, sdkErr)
g.Set(ctx, math.NaN(), sdk.Labels()) c.Add(ctx, math.NaN(), sdk.Labels())
require.Error(t, sdkErr) require.Error(t, sdkErr)
} }

View File

@ -17,11 +17,10 @@ Package metric implements the OpenTelemetry metric.Meter API. The SDK
supports configurable metrics export behavior through a collection of supports configurable metrics export behavior through a collection of
export interfaces that support various export strategies, described below. export interfaces that support various export strategies, described below.
The metric.Meter API consists of methods for constructing each of the The metric.Meter API consists of methods for constructing each of the basic
basic kinds of metric instrument. There are eight types of instrument kinds of metric instrument. There are six types of instrument available to
available to the end user, comprised of four basic kinds of metric the end user, comprised of three basic kinds of metric instrument (Counter,
instrument (Counter, Gauge, Measure, Observer) crossed with two kinds Measure, Observer) crossed with two kinds of number (int64, float64).
of number (int64, float64).
The API assists the SDK by consolidating the variety of metric instruments The API assists the SDK by consolidating the variety of metric instruments
into a narrower interface, allowing the SDK to avoid repetition of into a narrower interface, allowing the SDK to avoid repetition of
@ -31,25 +30,25 @@ numerical value.
To this end, the API uses a core.Number type to represent either an int64 To this end, the API uses a core.Number type to represent either an int64
or a float64, depending on the instrument's definition. A single or a float64, depending on the instrument's definition. A single
implementation interface is used for counter, gauge and measure implementation interface is used for counter and measure instruments,
instruments, metric.InstrumentImpl, and a single implementation interface metric.InstrumentImpl, and a single implementation interface is used for
is used for their handles, metric.HandleImpl. For observers, the API their handles, metric.HandleImpl. For observers, the API defines
defines interfaces, for which the SDK provides an implementation. interfaces, for which the SDK provides an implementation.
There are four entry points for events in the Metrics API - three for There are four entry points for events in the Metrics API - three for
synchronous instruments (counters, gauges and measures) and one for synchronous instruments (counters and measures) and one for asynchronous
asynchronous instruments (observers). The entry points for synchronous instruments (observers). The entry points for synchronous instruments are:
instruments are: via instrument handles, via direct instrument calls, and via instrument handles, via direct instrument calls, and via BatchRecord.
via BatchRecord. The SDK is designed with handles as the primary entry The SDK is designed with handles as the primary entry point, the other two
point, the other two entry points are implemented in terms of short-lived entry points are implemented in terms of short-lived handles. For example,
handles. For example, the implementation of a direct call allocates a the implementation of a direct call allocates a handle, operates on the
handle, operates on the handle, and releases the handle. Similarly, the handle, and releases the handle. Similarly, the implementation of
implementation of RecordBatch uses a short-lived handle for each RecordBatch uses a short-lived handle for each measurement in the batch.
measurement in the batch. The entry point for asynchronous instruments is The entry point for asynchronous instruments is via observer callbacks.
via observer callbacks. Observer callbacks behave like a set of instrument Observer callbacks behave like a set of instrument handles - one for each
handles - one for each observation for a distinct label set. The observer observation for a distinct label set. The observer handles are alive as
handles are alive as long as they are used. If the callback stops long as they are used. If the callback stops reporting values for a
reporting values for a certain label set, the associated handle is dropped. certain label set, the associated handle is dropped.
Internal Structure Internal Structure
@ -98,22 +97,21 @@ enters the SDK resulting in a new record, and collection context,
where a system-level thread performs a collection pass through the where a system-level thread performs a collection pass through the
SDK. SDK.
Descriptor is a struct that describes the metric instrument to the Descriptor is a struct that describes the metric instrument to the export
export pipeline, containing the name, recommended aggregation keys, pipeline, containing the name, recommended aggregation keys, units,
units, description, metric kind (counter, gauge, or measure), number description, metric kind (counter or measure), number kind (int64 or
kind (int64 or float64), and whether the instrument has alternate float64), and whether the instrument has alternate semantics or not (i.e.,
semantics or not (i.e., monotonic=false counter, monotonic=true gauge, monotonic=false counter, absolute=false measure). A Descriptor accompanies
absolute=false measure). A Descriptor accompanies metric data as it metric data as it passes through the export pipeline.
passes through the export pipeline.
The AggregationSelector interface supports choosing the method of The AggregationSelector interface supports choosing the method of
aggregation to apply to a particular instrument. Given the aggregation to apply to a particular instrument. Given the Descriptor,
Descriptor, this AggregatorFor method returns an implementation of this AggregatorFor method returns an implementation of Aggregator. If this
Aggregator. If this interface returns nil, the metric will be interface returns nil, the metric will be disabled. The aggregator should
disabled. The aggregator should be matched to the capabilities of the be matched to the capabilities of the exporter. Selecting the aggregator
exporter. Selecting the aggregator for counter and gauge instruments for counter instruments is relatively straightforward, but for measure and
is relatively straightforward, but for measure instruments there are observer instruments there are numerous choices with different cost and
numerous choices with different cost and quality tradeoffs. quality tradeoffs.
Aggregator is an interface which implements a concrete strategy for Aggregator is an interface which implements a concrete strategy for
aggregating metric updates. Several Aggregator implementations are aggregating metric updates. Several Aggregator implementations are

View File

@ -1,143 +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_test
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/api/core"
"go.opentelemetry.io/otel/api/key"
"go.opentelemetry.io/otel/api/metric"
export "go.opentelemetry.io/otel/sdk/export/metric"
"go.opentelemetry.io/otel/sdk/export/metric/aggregator"
sdk "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/metric/aggregator/gauge"
)
type monotoneBatcher struct {
// currentValue needs to be aligned for 64-bit atomic operations.
currentValue *core.Number
collections int
currentTime *time.Time
t *testing.T
}
func (*monotoneBatcher) AggregatorFor(*export.Descriptor) export.Aggregator {
return gauge.New()
}
func (*monotoneBatcher) CheckpointSet() export.CheckpointSet {
return nil
}
func (*monotoneBatcher) FinishedCollection() {
}
func (m *monotoneBatcher) Process(_ context.Context, record export.Record) error {
require.Equal(m.t, "my.gauge.name", record.Descriptor().Name())
require.Equal(m.t, 1, record.Labels().Len())
require.Equal(m.t, "a", string(record.Labels().Ordered()[0].Key))
require.Equal(m.t, "b", record.Labels().Ordered()[0].Value.Emit())
gauge := record.Aggregator().(*gauge.Aggregator)
val, ts, err := gauge.LastValue()
require.Nil(m.t, err)
m.currentValue = &val
m.currentTime = &ts
m.collections++
return nil
}
func TestMonotoneGauge(t *testing.T) {
ctx := context.Background()
batcher := &monotoneBatcher{
t: t,
}
sdk := sdk.New(batcher, sdk.NewDefaultLabelEncoder())
sdk.SetErrorHandler(func(error) { t.Fatal("Unexpected") })
gauge := sdk.NewInt64Gauge("my.gauge.name", metric.WithMonotonic(true))
handle := gauge.Bind(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.
var err error
sdk.SetErrorHandler(func(sdkErr error) {
err = sdkErr
})
handle.Set(ctx, 1)
require.Equal(t, aggregator.ErrNonMonotoneInput, err)
sdk.SetErrorHandler(func(error) { t.Fatal("Unexpected") })
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))
}

View File

@ -408,12 +408,6 @@ func (m *SDK) newCounterInstrument(name string, numberKind core.NumberKind, cos
return m.newInstrument(name, export.CounterKind, numberKind, &opts) return m.newInstrument(name, export.CounterKind, 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.GaugeKind, numberKind, &opts)
}
func (m *SDK) newMeasureInstrument(name string, numberKind core.NumberKind, mos ...api.MeasureOptionApplier) *instrument { func (m *SDK) newMeasureInstrument(name string, numberKind core.NumberKind, mos ...api.MeasureOptionApplier) *instrument {
opts := api.Options{} opts := api.Options{}
api.ApplyMeasureOptions(&opts, mos...) api.ApplyMeasureOptions(&opts, mos...)
@ -428,14 +422,6 @@ func (m *SDK) NewFloat64Counter(name string, cos ...api.CounterOptionApplier) ap
return api.WrapFloat64CounterInstrument(m.newCounterInstrument(name, core.Float64NumberKind, cos...)) 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 { func (m *SDK) NewInt64Measure(name string, mos ...api.MeasureOptionApplier) api.Int64Measure {
return api.WrapInt64MeasureInstrument(m.newMeasureInstrument(name, core.Int64NumberKind, mos...)) return api.WrapInt64MeasureInstrument(m.newMeasureInstrument(name, core.Int64NumberKind, mos...))
} }

View File

@ -19,7 +19,6 @@ import (
"go.opentelemetry.io/otel/sdk/metric/aggregator/array" "go.opentelemetry.io/otel/sdk/metric/aggregator/array"
"go.opentelemetry.io/otel/sdk/metric/aggregator/counter" "go.opentelemetry.io/otel/sdk/metric/aggregator/counter"
"go.opentelemetry.io/otel/sdk/metric/aggregator/ddsketch" "go.opentelemetry.io/otel/sdk/metric/aggregator/ddsketch"
"go.opentelemetry.io/otel/sdk/metric/aggregator/gauge"
"go.opentelemetry.io/otel/sdk/metric/aggregator/minmaxsumcount" "go.opentelemetry.io/otel/sdk/metric/aggregator/minmaxsumcount"
) )
@ -38,17 +37,17 @@ var (
) )
// NewWithInexpensiveMeasure returns a simple aggregation selector // NewWithInexpensiveMeasure returns a simple aggregation selector
// that uses counter, gauge, and minmaxsumcount aggregators for the // that uses counter, minmaxsumcount and minmaxsumcount aggregators
// four kinds of metric. This selector is faster and uses less memory // for the three kinds of metric. This selector is faster and uses
// than the others because minmaxsumcount does not aggregate quantile // less memory than the others because minmaxsumcount does not
// information. // aggregate quantile information.
func NewWithInexpensiveMeasure() export.AggregationSelector { func NewWithInexpensiveMeasure() export.AggregationSelector {
return selectorInexpensive{} return selectorInexpensive{}
} }
// NewWithSketchMeasure returns a simple aggregation selector that // NewWithSketchMeasure returns a simple aggregation selector that
// uses counter, gauge, and ddsketch aggregators for the four kinds of // uses counter, ddsketch, and ddsketch aggregators for the three
// metric. This selector uses more cpu and memory than the // kinds of metric. This selector uses more cpu and memory than the
// NewWithInexpensiveMeasure because it uses one DDSketch per distinct // NewWithInexpensiveMeasure because it uses one DDSketch per distinct
// measure/observer and labelset. // measure/observer and labelset.
func NewWithSketchMeasure(config *ddsketch.Config) export.AggregationSelector { func NewWithSketchMeasure(config *ddsketch.Config) export.AggregationSelector {
@ -58,7 +57,7 @@ func NewWithSketchMeasure(config *ddsketch.Config) export.AggregationSelector {
} }
// NewWithExactMeasure returns a simple aggregation selector that uses // NewWithExactMeasure returns a simple aggregation selector that uses
// counter, gauge, and array aggregators for the four kinds of metric. // counter, array, and array aggregators for the three kinds of metric.
// This selector uses more memory than the NewWithSketchMeasure // This selector uses more memory than the NewWithSketchMeasure
// because it aggregates an array of all values, therefore is able to // because it aggregates an array of all values, therefore is able to
// compute exact quantiles. // compute exact quantiles.
@ -68,8 +67,6 @@ func NewWithExactMeasure() export.AggregationSelector {
func (selectorInexpensive) AggregatorFor(descriptor *export.Descriptor) export.Aggregator { func (selectorInexpensive) AggregatorFor(descriptor *export.Descriptor) export.Aggregator {
switch descriptor.MetricKind() { switch descriptor.MetricKind() {
case export.GaugeKind:
return gauge.New()
case export.ObserverKind: case export.ObserverKind:
fallthrough fallthrough
case export.MeasureKind: case export.MeasureKind:
@ -81,8 +78,6 @@ func (selectorInexpensive) AggregatorFor(descriptor *export.Descriptor) export.A
func (s selectorSketch) AggregatorFor(descriptor *export.Descriptor) export.Aggregator { func (s selectorSketch) AggregatorFor(descriptor *export.Descriptor) export.Aggregator {
switch descriptor.MetricKind() { switch descriptor.MetricKind() {
case export.GaugeKind:
return gauge.New()
case export.ObserverKind: case export.ObserverKind:
fallthrough fallthrough
case export.MeasureKind: case export.MeasureKind:
@ -94,8 +89,6 @@ func (s selectorSketch) AggregatorFor(descriptor *export.Descriptor) export.Aggr
func (selectorExact) AggregatorFor(descriptor *export.Descriptor) export.Aggregator { func (selectorExact) AggregatorFor(descriptor *export.Descriptor) export.Aggregator {
switch descriptor.MetricKind() { switch descriptor.MetricKind() {
case export.GaugeKind:
return gauge.New()
case export.ObserverKind: case export.ObserverKind:
fallthrough fallthrough
case export.MeasureKind: case export.MeasureKind:

View File

@ -24,13 +24,11 @@ import (
"go.opentelemetry.io/otel/sdk/metric/aggregator/array" "go.opentelemetry.io/otel/sdk/metric/aggregator/array"
"go.opentelemetry.io/otel/sdk/metric/aggregator/counter" "go.opentelemetry.io/otel/sdk/metric/aggregator/counter"
"go.opentelemetry.io/otel/sdk/metric/aggregator/ddsketch" "go.opentelemetry.io/otel/sdk/metric/aggregator/ddsketch"
"go.opentelemetry.io/otel/sdk/metric/aggregator/gauge"
"go.opentelemetry.io/otel/sdk/metric/aggregator/minmaxsumcount" "go.opentelemetry.io/otel/sdk/metric/aggregator/minmaxsumcount"
"go.opentelemetry.io/otel/sdk/metric/selector/simple" "go.opentelemetry.io/otel/sdk/metric/selector/simple"
) )
var ( var (
testGaugeDesc = export.NewDescriptor("gauge", export.GaugeKind, nil, "", "", core.Int64NumberKind, false)
testCounterDesc = export.NewDescriptor("counter", export.CounterKind, nil, "", "", core.Int64NumberKind, false) testCounterDesc = export.NewDescriptor("counter", export.CounterKind, nil, "", "", core.Int64NumberKind, false)
testMeasureDesc = export.NewDescriptor("measure", export.MeasureKind, nil, "", "", core.Int64NumberKind, false) testMeasureDesc = export.NewDescriptor("measure", export.MeasureKind, nil, "", "", core.Int64NumberKind, false)
testObserverDesc = export.NewDescriptor("observer", export.ObserverKind, nil, "", "", core.Int64NumberKind, false) testObserverDesc = export.NewDescriptor("observer", export.ObserverKind, nil, "", "", core.Int64NumberKind, false)
@ -38,7 +36,6 @@ var (
func TestInexpensiveMeasure(t *testing.T) { func TestInexpensiveMeasure(t *testing.T) {
inex := simple.NewWithInexpensiveMeasure() inex := simple.NewWithInexpensiveMeasure()
require.NotPanics(t, func() { _ = inex.AggregatorFor(testGaugeDesc).(*gauge.Aggregator) })
require.NotPanics(t, func() { _ = inex.AggregatorFor(testCounterDesc).(*counter.Aggregator) }) require.NotPanics(t, func() { _ = inex.AggregatorFor(testCounterDesc).(*counter.Aggregator) })
require.NotPanics(t, func() { _ = inex.AggregatorFor(testMeasureDesc).(*minmaxsumcount.Aggregator) }) require.NotPanics(t, func() { _ = inex.AggregatorFor(testMeasureDesc).(*minmaxsumcount.Aggregator) })
require.NotPanics(t, func() { _ = inex.AggregatorFor(testObserverDesc).(*minmaxsumcount.Aggregator) }) require.NotPanics(t, func() { _ = inex.AggregatorFor(testObserverDesc).(*minmaxsumcount.Aggregator) })
@ -46,7 +43,6 @@ func TestInexpensiveMeasure(t *testing.T) {
func TestSketchMeasure(t *testing.T) { func TestSketchMeasure(t *testing.T) {
sk := simple.NewWithSketchMeasure(ddsketch.NewDefaultConfig()) sk := simple.NewWithSketchMeasure(ddsketch.NewDefaultConfig())
require.NotPanics(t, func() { _ = sk.AggregatorFor(testGaugeDesc).(*gauge.Aggregator) })
require.NotPanics(t, func() { _ = sk.AggregatorFor(testCounterDesc).(*counter.Aggregator) }) require.NotPanics(t, func() { _ = sk.AggregatorFor(testCounterDesc).(*counter.Aggregator) })
require.NotPanics(t, func() { _ = sk.AggregatorFor(testMeasureDesc).(*ddsketch.Aggregator) }) require.NotPanics(t, func() { _ = sk.AggregatorFor(testMeasureDesc).(*ddsketch.Aggregator) })
require.NotPanics(t, func() { _ = sk.AggregatorFor(testObserverDesc).(*ddsketch.Aggregator) }) require.NotPanics(t, func() { _ = sk.AggregatorFor(testObserverDesc).(*ddsketch.Aggregator) })
@ -54,7 +50,6 @@ func TestSketchMeasure(t *testing.T) {
func TestExactMeasure(t *testing.T) { func TestExactMeasure(t *testing.T) {
ex := simple.NewWithExactMeasure() ex := simple.NewWithExactMeasure()
require.NotPanics(t, func() { _ = ex.AggregatorFor(testGaugeDesc).(*gauge.Aggregator) })
require.NotPanics(t, func() { _ = ex.AggregatorFor(testCounterDesc).(*counter.Aggregator) }) require.NotPanics(t, func() { _ = ex.AggregatorFor(testCounterDesc).(*counter.Aggregator) })
require.NotPanics(t, func() { _ = ex.AggregatorFor(testMeasureDesc).(*array.Aggregator) }) require.NotPanics(t, func() { _ = ex.AggregatorFor(testMeasureDesc).(*array.Aggregator) })
require.NotPanics(t, func() { _ = ex.AggregatorFor(testObserverDesc).(*array.Aggregator) }) require.NotPanics(t, func() { _ = ex.AggregatorFor(testObserverDesc).(*array.Aggregator) })

View File

@ -39,7 +39,7 @@ import (
"go.opentelemetry.io/otel/sdk/export/metric/aggregator" "go.opentelemetry.io/otel/sdk/export/metric/aggregator"
sdk "go.opentelemetry.io/otel/sdk/metric" sdk "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/metric/aggregator/counter" "go.opentelemetry.io/otel/sdk/metric/aggregator/counter"
"go.opentelemetry.io/otel/sdk/metric/aggregator/gauge" "go.opentelemetry.io/otel/sdk/metric/aggregator/lastvalue"
) )
const ( const (
@ -78,7 +78,7 @@ type (
newStore func() interface{} newStore func() interface{}
// storeCollect and storeExpect are the same for // storeCollect and storeExpect are the same for
// counters, different for gauges, to ensure we are // counters, different for lastValues, to ensure we are
// testing the timestamps correctly. // testing the timestamps correctly.
storeCollect func(store interface{}, value core.Number, ts time.Time) storeCollect func(store interface{}, value core.Number, ts time.Time)
storeExpect func(store interface{}, value core.Number) storeExpect func(store interface{}, value core.Number)
@ -90,10 +90,10 @@ type (
Impl() metric.InstrumentImpl Impl() metric.InstrumentImpl
} }
// gaugeState supports merging gauge values, for the case // lastValueState supports merging lastValue values, for the case
// where a race condition causes duplicate records. We always // where a race condition causes duplicate records. We always
// take the later timestamp. // take the later timestamp.
gaugeState struct { lastValueState struct {
// raw has to be aligned for 64-bit atomic operations. // raw has to be aligned for 64-bit atomic operations.
raw core.Number raw core.Number
ts time.Time ts time.Time
@ -231,11 +231,12 @@ func (f *testFixture) preCollect() {
} }
func (*testFixture) AggregatorFor(descriptor *export.Descriptor) export.Aggregator { func (*testFixture) AggregatorFor(descriptor *export.Descriptor) export.Aggregator {
switch descriptor.MetricKind() { name := descriptor.Name()
case export.CounterKind: switch {
case strings.HasSuffix(name, "counter"):
return counter.New() return counter.New()
case export.GaugeKind: case strings.HasSuffix(name, "lastvalue"):
return gauge.New() return lastvalue.New()
default: default:
panic("Not implemented for this test") panic("Not implemented for this test")
} }
@ -270,9 +271,9 @@ func (f *testFixture) Process(_ context.Context, record export.Record) error {
f.T.Fatal("Sum error: ", err) f.T.Fatal("Sum error: ", err)
} }
f.impl.storeCollect(actual, sum, time.Time{}) f.impl.storeCollect(actual, sum, time.Time{})
case export.GaugeKind: case export.MeasureKind:
gauge := agg.(aggregator.LastValue) lvagg := agg.(aggregator.LastValue)
lv, ts, err := gauge.LastValue() lv, ts, err := lvagg.LastValue()
if err != nil && err != aggregator.ErrNoLastValue { if err != nil && err != aggregator.ErrNoLastValue {
f.T.Fatal("Last value error: ", err) f.T.Fatal("Last value error: ", err)
} }
@ -338,7 +339,7 @@ func float64sEqual(a, b core.Number) bool {
func intCounterTestImpl(nonMonotonic bool) testImpl { func intCounterTestImpl(nonMonotonic bool) testImpl {
return testImpl{ return testImpl{
newInstrument: func(meter api.Meter, name string) withImpl { newInstrument: func(meter api.Meter, name string) withImpl {
return meter.NewInt64Counter(name, api.WithMonotonic(!nonMonotonic)) return meter.NewInt64Counter(name+".counter", api.WithMonotonic(!nonMonotonic))
}, },
getUpdateValue: func() core.Number { getUpdateValue: func() core.Number {
var offset int64 var offset int64
@ -384,7 +385,7 @@ func TestStressInt64CounterNonMonotonic(t *testing.T) {
func floatCounterTestImpl(nonMonotonic bool) testImpl { func floatCounterTestImpl(nonMonotonic bool) testImpl {
return testImpl{ return testImpl{
newInstrument: func(meter api.Meter, name string) withImpl { newInstrument: func(meter api.Meter, name string) withImpl {
return meter.NewFloat64Counter(name, api.WithMonotonic(!nonMonotonic)) return meter.NewFloat64Counter(name+".counter", api.WithMonotonic(!nonMonotonic))
}, },
getUpdateValue: func() core.Number { getUpdateValue: func() core.Number {
var offset float64 var offset float64
@ -427,34 +428,28 @@ func TestStressFloat64CounterNonMonotonic(t *testing.T) {
stressTest(t, floatCounterTestImpl(true)) stressTest(t, floatCounterTestImpl(true))
} }
// Gauges // LastValue
func intGaugeTestImpl(monotonic bool) testImpl {
// (Now()-startTime) is used as a free monotonic source
startTime := time.Now()
func intLastValueTestImpl() testImpl {
return testImpl{ return testImpl{
newInstrument: func(meter api.Meter, name string) withImpl { newInstrument: func(meter api.Meter, name string) withImpl {
return meter.NewInt64Gauge(name, api.WithMonotonic(monotonic)) return meter.NewInt64Measure(name+".lastvalue", metric.WithAbsolute(false))
}, },
getUpdateValue: func() core.Number { getUpdateValue: func() core.Number {
if !monotonic { r1 := rand.Int63()
r1 := rand.Int63() return core.NewInt64Number(rand.Int63() - r1)
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) { operate: func(inst interface{}, ctx context.Context, value core.Number, labels api.LabelSet) {
gauge := inst.(api.Int64Gauge) measure := inst.(api.Int64Measure)
gauge.Set(ctx, value.AsInt64(), labels) measure.Record(ctx, value.AsInt64(), labels)
}, },
newStore: func() interface{} { newStore: func() interface{} {
return &gaugeState{ return &lastValueState{
raw: core.NewInt64Number(0), raw: core.NewInt64Number(0),
} }
}, },
storeCollect: func(store interface{}, value core.Number, ts time.Time) { storeCollect: func(store interface{}, value core.Number, ts time.Time) {
gs := store.(*gaugeState) gs := store.(*lastValueState)
if !ts.Before(gs.ts) { if !ts.Before(gs.ts) {
gs.ts = ts gs.ts = ts
@ -462,50 +457,40 @@ func intGaugeTestImpl(monotonic bool) testImpl {
} }
}, },
storeExpect: func(store interface{}, value core.Number) { storeExpect: func(store interface{}, value core.Number) {
gs := store.(*gaugeState) gs := store.(*lastValueState)
gs.raw.SetInt64Atomic(value.AsInt64()) gs.raw.SetInt64Atomic(value.AsInt64())
}, },
readStore: func(store interface{}) core.Number { readStore: func(store interface{}) core.Number {
gs := store.(*gaugeState) gs := store.(*lastValueState)
return gs.raw.AsNumberAtomic() return gs.raw.AsNumberAtomic()
}, },
equalValues: int64sEqual, equalValues: int64sEqual,
} }
} }
func TestStressInt64GaugeNormal(t *testing.T) { func TestStressInt64LastValue(t *testing.T) {
stressTest(t, intGaugeTestImpl(false)) stressTest(t, intLastValueTestImpl())
} }
func TestStressInt64GaugeMonotonic(t *testing.T) { func floatLastValueTestImpl() testImpl {
stressTest(t, intGaugeTestImpl(true))
}
func floatGaugeTestImpl(monotonic bool) testImpl {
// (Now()-startTime) is used as a free monotonic source
startTime := time.Now()
return testImpl{ return testImpl{
newInstrument: func(meter api.Meter, name string) withImpl { newInstrument: func(meter api.Meter, name string) withImpl {
return meter.NewFloat64Gauge(name, api.WithMonotonic(monotonic)) return meter.NewFloat64Measure(name+".lastvalue", metric.WithAbsolute(false))
}, },
getUpdateValue: func() core.Number { getUpdateValue: func() core.Number {
if !monotonic { return core.NewFloat64Number((-0.5 + rand.Float64()) * 100000)
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) { operate: func(inst interface{}, ctx context.Context, value core.Number, labels api.LabelSet) {
gauge := inst.(api.Float64Gauge) measure := inst.(api.Float64Measure)
gauge.Set(ctx, value.AsFloat64(), labels) measure.Record(ctx, value.AsFloat64(), labels)
}, },
newStore: func() interface{} { newStore: func() interface{} {
return &gaugeState{ return &lastValueState{
raw: core.NewFloat64Number(0), raw: core.NewFloat64Number(0),
} }
}, },
storeCollect: func(store interface{}, value core.Number, ts time.Time) { storeCollect: func(store interface{}, value core.Number, ts time.Time) {
gs := store.(*gaugeState) gs := store.(*lastValueState)
if !ts.Before(gs.ts) { if !ts.Before(gs.ts) {
gs.ts = ts gs.ts = ts
@ -513,21 +498,17 @@ func floatGaugeTestImpl(monotonic bool) testImpl {
} }
}, },
storeExpect: func(store interface{}, value core.Number) { storeExpect: func(store interface{}, value core.Number) {
gs := store.(*gaugeState) gs := store.(*lastValueState)
gs.raw.SetFloat64Atomic(value.AsFloat64()) gs.raw.SetFloat64Atomic(value.AsFloat64())
}, },
readStore: func(store interface{}) core.Number { readStore: func(store interface{}) core.Number {
gs := store.(*gaugeState) gs := store.(*lastValueState)
return gs.raw.AsNumberAtomic() return gs.raw.AsNumberAtomic()
}, },
equalValues: float64sEqual, equalValues: float64sEqual,
} }
} }
func TestStressFloat64GaugeNormal(t *testing.T) { func TestStressFloat64LastValue(t *testing.T) {
stressTest(t, floatGaugeTestImpl(false)) stressTest(t, floatLastValueTestImpl())
}
func TestStressFloat64GaugeMonotonic(t *testing.T) {
stressTest(t, floatGaugeTestImpl(true))
} }