1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2024-12-12 10:04:29 +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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 333 additions and 1048 deletions

View File

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

View File

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

View File

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

View File

@ -15,10 +15,9 @@
// metric package provides an API for reporting diagnostic
// measurements using four basic kinds of instruments.
//
// The four basic kinds are:
// The three basic kinds are:
//
// - counters
// - gauges
// - measures
// - observers
//
@ -42,15 +41,6 @@
// function - this allows reporting negative values. To report the new
// 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
// recorded separately to figure out some statistical properties from
// those values (like average). An example could be temperature over
@ -74,11 +64,11 @@
// callback can report multiple values. To unregister the observer,
// call Unregister on it.
//
// Counters, gauges and measures support creating bound instruments
// for a potentially more efficient reporting. The bound instruments
// have the same function names as the instruments (so a Counter bound
// instrument has Add, a Gauge bound instrument has Set, and a Measure
// bound instrument has Record). Bound Instruments can be created
// with the Bind function of the respective instrument. When done with
// the bound instrument, call Unbind on it.
// Counters and measures support creating bound instruments for a
// potentially more efficient reporting. The bound instruments have
// the same function names as the instruments (so a Counter bound
// instrument has Add, and a Measure bound instrument has Record).
// Bound Instruments can be created with the Bind function of the
// respective instrument. When done with the bound instrument, call
// Unbind on it.
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{})
}
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 {
return WrapInt64MeasureInstrument(noopInstrument{})
}

View File

@ -67,22 +67,6 @@ func WrapFloat64CounterInstrument(instrument InstrumentImpl) Float64Counter {
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
// 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
// options to passed opts.
func ApplyMeasureOptions(opts *Options, mos ...MeasureOptionApplier) {

View File

@ -73,10 +73,16 @@ func main() {
tracer := global.TraceProvider().Tracer("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.WithDescription("A gauge set to 1.0"),
metric.WithDescription("An observer set to 1.0"),
)
defer oneMetric.Unregister()
measureTwo := meter.NewFloat64Measure("ex.com.two")
@ -87,11 +93,6 @@ func main() {
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)
defer measure.Unbind()
@ -101,14 +102,11 @@ func main() {
trace.SpanFromContext(ctx).SetAttributes(anotherKey.String("yes"))
gauge.Set(ctx, 1)
meter.RecordBatch(
// Note: call-site variables added as context Entries:
correlation.NewContext(ctx, anotherKey.String("xyz")),
commonLabels,
oneMetric.Measurement(1.0),
measureTwo.Measurement(2.0),
)

View File

@ -18,6 +18,7 @@ import (
"context"
"log"
"net/http"
"sync"
"time"
"go.opentelemetry.io/otel/api/global"
@ -50,11 +51,21 @@ func main() {
defer initMeter().Stop()
meter := global.MeterProvider().Meter("ex.com/basic")
oneMetric := meter.NewFloat64Gauge("ex.com.one",
observerLock := new(sync.RWMutex)
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.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")))
measureThree := meter.NewFloat64Counter("ex.com.three")
@ -64,28 +75,39 @@ func main() {
ctx := context.Background()
(*observerLock).Lock()
*observerValueToReport = 1.0
*observerLabelSetToReport = &commonLabels
(*observerLock).Unlock()
meter.RecordBatch(
ctx,
commonLabels,
oneMetric.Measurement(1.0),
measureTwo.Measurement(2.0),
measureThree.Measurement(12.0),
)
time.Sleep(5 * time.Second)
(*observerLock).Lock()
*observerValueToReport = 1.0
*observerLabelSetToReport = &notSoCommonLabels
(*observerLock).Unlock()
meter.RecordBatch(
ctx,
notSoCommonLabels,
oneMetric.Measurement(1.0),
measureTwo.Measurement(2.0),
measureThree.Measurement(22.0),
)
time.Sleep(5 * time.Second)
(*observerLock).Lock()
*observerValueToReport = 13.0
*observerLabelSetToReport = &commonLabels
(*observerLock).Unlock()
meter.RecordBatch(
ctx,
commonLabels,
oneMetric.Measurement(13.0),
measureTwo.Measurement(12.0),
measureThree.Measurement(13.0),
)

View File

@ -92,13 +92,13 @@ func TestBasicFormat(t *testing.T) {
for _, ao := range []adapterOutput{{
adapter: newWithTagsAdapter(),
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
timer:%s|ms|#A:B,C:D
`}, {
adapter: newNoTagsAdapter(),
expected: `counter.B.D:%s|c
gauge.B.D:%s|g
observer.B.D:%s|g
measure.B.D:%s|h
timer.B.D:%s|ms
`},
@ -126,7 +126,7 @@ timer.B.D:%s|ms
cdesc := export.NewDescriptor(
"counter", export.CounterKind, nil, "", "", nkind, false)
gdesc := export.NewDescriptor(
"gauge", export.GaugeKind, nil, "", "", nkind, false)
"observer", export.ObserverKind, nil, "", "", nkind, false)
mdesc := export.NewDescriptor(
"measure", export.MeasureKind, nil, "", "", nkind, false)
tdesc := export.NewDescriptor(
@ -139,7 +139,7 @@ timer.B.D:%s|ms
const value = 123.456
checkpointSet.AddCounter(cdesc, value, labels...)
checkpointSet.AddGauge(gdesc, value, labels...)
checkpointSet.AddLastValue(gdesc, value, labels...)
checkpointSet.AddMeasure(mdesc, 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)
} else if sum, ok := agg.(aggregator.Sum); ok {
c.exportCounter(ch, sum, numberKind, desc, labels)
} else if gauge, ok := agg.(aggregator.LastValue); ok {
c.exportGauge(ch, gauge, numberKind, desc, labels)
} else if lastValue, ok := agg.(aggregator.LastValue); ok {
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) {
lastValue, _, err := gauge.LastValue()
func (c *collector) exportLastValue(ch chan<- prometheus.Metric, lvagg aggregator.LastValue, kind core.NumberKind, desc *prometheus.Desc, labels []string) {
lv, _, err := lvagg.LastValue()
if err != nil {
c.exp.onError(err)
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 {
c.exp.onError(err)
return

View File

@ -31,8 +31,8 @@ func TestPrometheusExporter(t *testing.T) {
counter := export.NewDescriptor(
"counter", export.CounterKind, nil, "", "", core.Float64NumberKind, false)
gauge := export.NewDescriptor(
"gauge", export.GaugeKind, nil, "", "", core.Float64NumberKind, false)
lastValue := export.NewDescriptor(
"lastvalue", export.ObserverKind, nil, "", "", core.Float64NumberKind, false)
measure := export.NewDescriptor(
"measure", export.MeasureKind, nil, "", "", core.Float64NumberKind, false)
@ -44,8 +44,8 @@ func TestPrometheusExporter(t *testing.T) {
checkpointSet.AddCounter(counter, 15.3, labels...)
expected = append(expected, `counter{A="B",C="D"} 15.3`)
checkpointSet.AddGauge(gauge, 13.2, labels...)
expected = append(expected, `gauge{A="B",C="D"} 13.2`)
checkpointSet.AddLastValue(lastValue, 13.2, labels...)
expected = append(expected, `lastvalue{A="B",C="D"} 13.2`)
checkpointSet.AddMeasure(measure, 13, labels...)
checkpointSet.AddMeasure(measure, 15, labels...)
@ -64,8 +64,8 @@ func TestPrometheusExporter(t *testing.T) {
checkpointSet.AddCounter(counter, 12, missingLabels...)
expected = append(expected, `counter{A="E",C=""} 12`)
checkpointSet.AddGauge(gauge, 32, missingLabels...)
expected = append(expected, `gauge{A="E",C=""} 32`)
checkpointSet.AddLastValue(lastValue, 32, missingLabels...)
expected = append(expected, `lastvalue{A="E",C=""} 32`)
checkpointSet.AddMeasure(measure, 19, missingLabels...)
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/counter"
"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"
aggtest "go.opentelemetry.io/otel/sdk/metric/aggregator/test"
)
@ -82,12 +82,12 @@ func TestStdoutTimestamp(t *testing.T) {
checkpointSet := test.NewCheckpointSet(sdk.NewDefaultLabelEncoder())
ctx := context.Background()
desc := export.NewDescriptor("test.name", export.GaugeKind, nil, "", "", core.Int64NumberKind, false)
gagg := gauge.New()
aggtest.CheckedUpdate(t, gagg, core.NewInt64Number(321), desc)
gagg.Checkpoint(ctx, desc)
desc := export.NewDescriptor("test.name", export.ObserverKind, nil, "", "", core.Int64NumberKind, false)
lvagg := lastvalue.New()
aggtest.CheckedUpdate(t, lvagg, core.NewInt64Number(321), desc)
lvagg.Checkpoint(ctx, desc)
checkpointSet.Add(desc, gagg)
checkpointSet.Add(desc, lvagg)
if err := exporter.Export(ctx, checkpointSet); err != nil {
t.Fatal("Unexpected export error: ", err)
@ -107,19 +107,19 @@ func TestStdoutTimestamp(t *testing.T) {
t.Fatal("JSON parse error: ", updateTS, ": ", err)
}
gaugeTS := printed["updates"].([]interface{})[0].(map[string]interface{})["time"].(string)
gaugeTimestamp, err := time.Parse(time.RFC3339Nano, gaugeTS)
lastValueTS := printed["updates"].([]interface{})[0].(map[string]interface{})["time"].(string)
lastValueTimestamp, err := time.Parse(time.RFC3339Nano, lastValueTS)
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.Before(after))
require.True(t, gaugeTimestamp.After(before))
require.True(t, gaugeTimestamp.Before(after))
require.True(t, lastValueTimestamp.After(before))
require.True(t, lastValueTimestamp.Before(after))
require.True(t, gaugeTimestamp.Before(updateTimestamp))
require.True(t, lastValueTimestamp.Before(updateTimestamp))
}
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())
}
func TestStdoutGaugeFormat(t *testing.T) {
func TestStdoutLastValueFormat(t *testing.T) {
fix := newFixture(t, stdout.Config{})
checkpointSet := test.NewCheckpointSet(sdk.NewDefaultLabelEncoder())
desc := export.NewDescriptor("test.name", export.GaugeKind, nil, "", "", core.Float64NumberKind, false)
gagg := gauge.New()
aggtest.CheckedUpdate(fix.t, gagg, core.NewFloat64Number(123.456), desc)
gagg.Checkpoint(fix.ctx, desc)
desc := export.NewDescriptor("test.name", export.ObserverKind, nil, "", "", core.Float64NumberKind, false)
lvagg := lastvalue.New()
aggtest.CheckedUpdate(fix.t, lvagg, core.NewFloat64Number(123.456), 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)
@ -247,16 +247,16 @@ func TestStdoutEmptyDataSet(t *testing.T) {
}
}
func TestStdoutGaugeNotSet(t *testing.T) {
func TestStdoutLastValueNotSet(t *testing.T) {
fix := newFixture(t, stdout.Config{})
checkpointSet := test.NewCheckpointSet(sdk.NewDefaultLabelEncoder())
desc := export.NewDescriptor("test.name", export.GaugeKind, nil, "", "", core.Float64NumberKind, false)
gagg := gauge.New()
gagg.Checkpoint(fix.ctx, desc)
desc := export.NewDescriptor("test.name", export.ObserverKind, nil, "", "", core.Float64NumberKind, false)
lvagg := lastvalue.New()
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)

View File

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

View File

@ -103,7 +103,6 @@ var (
const (
KindCounter Kind = iota
KindGauge
KindMeasure
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 {
instrument := m.newMeasureInstrument(name, core.Int64NumberKind, mos...)
return apimetric.WrapInt64MeasureInstrument(instrument)

View File

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

View File

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

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// 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 (
"context"
@ -25,25 +25,20 @@ import (
"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 (
// Aggregator aggregates gauge events.
// Aggregator aggregates lastValue events.
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
// checkpoint is a copy of the current value taken in Checkpoint()
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.
gaugeData struct {
lastValueData struct {
// value is the int64- or float64-encoded Set() data
//
// value needs to be aligned for 64-bit atomic operations.
@ -51,7 +46,7 @@ type (
// timestamp indicates when this record was submitted.
// this can be used to pick a winner when multiple
// records contain gauge data for the same labels due
// records contain lastValue data for the same labels due
// to races.
timestamp time.Time
}
@ -60,25 +55,25 @@ type (
var _ export.Aggregator = &Aggregator{}
var _ aggregator.LastValue = &Aggregator{}
// An unset gauge has zero timestamp and zero value.
var unsetGauge = &gaugeData{}
// An unset lastValue has zero timestamp and zero value.
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.
func New() *Aggregator {
return &Aggregator{
current: unsafe.Pointer(unsetGauge),
checkpoint: unsafe.Pointer(unsetGauge),
current: unsafe.Pointer(unsetLastValue),
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
// will be returned if (due to a race condition) the checkpoint was
// computed before the first value was set.
func (g *Aggregator) LastValue() (core.Number, time.Time, error) {
gd := (*gaugeData)(g.checkpoint)
if gd == unsetGauge {
gd := (*lastValueData)(g.checkpoint)
if gd == unsetLastValue {
return core.Number(0), time.Time{}, aggregator.ErrNoLastValue
}
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.
func (g *Aggregator) Update(_ context.Context, number core.Number, desc *export.Descriptor) error {
if !desc.Alternate() {
g.updateNonMonotonic(number)
return nil
}
return g.updateMonotonic(number, desc)
}
func (g *Aggregator) updateNonMonotonic(number core.Number) {
ngd := &gaugeData{
ngd := &lastValueData{
value: number,
timestamp: time.Now(),
}
atomic.StorePointer(&g.current, unsafe.Pointer(ngd))
return nil
}
func (g *Aggregator) updateMonotonic(number core.Number, desc *export.Descriptor) error {
ngd := &gaugeData{
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.
// Merge combines state from two aggregators. The most-recently set
// value is chosen.
func (g *Aggregator) Merge(oa export.Aggregator, desc *export.Descriptor) error {
o, _ := oa.(*Aggregator)
if o == nil {
return aggregator.NewInconsistentMergeError(g, oa)
}
ggd := (*gaugeData)(atomic.LoadPointer(&g.checkpoint))
ogd := (*gaugeData)(atomic.LoadPointer(&o.checkpoint))
ggd := (*lastValueData)(atomic.LoadPointer(&g.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) {
return nil
}

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package gauge
package lastvalue
import (
"context"
@ -38,8 +38,8 @@ var _ export.Aggregator = &Aggregator{}
func TestMain(m *testing.M) {
fields := []ottest.FieldOffset{
{
Name: "gaugeData.value",
Offset: unsafe.Offsetof(gaugeData{}.value),
Name: "lastValueData.value",
Offset: unsafe.Offsetof(lastValueData{}.value),
},
}
if !ottest.Aligned8Byte(fields, os.Stderr) {
@ -49,13 +49,13 @@ func TestMain(m *testing.M) {
os.Exit(m.Run())
}
func TestGaugeNonMonotonic(t *testing.T) {
func TestLastValueUpdate(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, false)
record := test.NewAggregatorTest(export.ObserverKind, profile.NumberKind, false)
var last core.Number
for i := 0; i < count; i++ {
@ -72,66 +72,14 @@ func TestGaugeNonMonotonic(t *testing.T) {
})
}
func TestGaugeMonotonic(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) {
func TestLastValueMerge(t *testing.T) {
ctx := context.Background()
test.RunProfiles(t, func(t *testing.T, profile test.Profile) {
agg1 := New()
agg2 := New()
descriptor := test.NewAggregatorTest(export.GaugeKind, profile.NumberKind, false)
descriptor := test.NewAggregatorTest(export.ObserverKind, profile.NumberKind, false)
first1 := profile.Random(+1)
first2 := profile.Random(+1)
@ -158,39 +106,8 @@ func TestGaugeNormalMerge(t *testing.T) {
})
}
func TestGaugeMonotonicMerge(t *testing.T) {
ctx := context.Background()
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)
func TestLastValueNotSet(t *testing.T) {
descriptor := test.NewAggregatorTest(export.ObserverKind, core.Int64NumberKind, true)
g := New()
g.Checkpoint(context.Background(), descriptor)

View File

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

View File

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

View File

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

View File

@ -28,7 +28,7 @@ import (
sdk "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/metric/aggregator/counter"
"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"
)
@ -47,14 +47,13 @@ func newFixture(b *testing.B) *benchFixture {
}
func (*benchFixture) AggregatorFor(descriptor *export.Descriptor) export.Aggregator {
switch descriptor.MetricKind() {
case export.CounterKind:
name := descriptor.Name()
switch {
case strings.HasSuffix(name, "counter"):
return counter.New()
case export.GaugeKind:
return gauge.New()
case export.ObserverKind:
fallthrough
case export.MeasureKind:
case strings.HasSuffix(name, "lastvalue"):
return lastvalue.New()
default:
if strings.HasSuffix(descriptor.Name(), "minmaxsumcount") {
return minmaxsumcount.New(descriptor)
} 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()
fix := newFixture(b)
labs := fix.sdk.Labels(makeLabels(1)...)
gau := fix.sdk.NewInt64Gauge("int64.gauge")
mea := fix.sdk.NewInt64Measure("int64.lastvalue")
b.ResetTimer()
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()
fix := newFixture(b)
labs := fix.sdk.Labels(makeLabels(1)...)
gau := fix.sdk.NewInt64Gauge("int64.gauge")
handle := gau.Bind(labs)
mea := fix.sdk.NewInt64Measure("int64.lastvalue")
handle := mea.Bind(labs)
b.ResetTimer()
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()
fix := newFixture(b)
labs := fix.sdk.Labels(makeLabels(1)...)
gau := fix.sdk.NewFloat64Gauge("float64.gauge")
mea := fix.sdk.NewFloat64Measure("float64.lastvalue")
b.ResetTimer()
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()
fix := newFixture(b)
labs := fix.sdk.Labels(makeLabels(1)...)
gau := fix.sdk.NewFloat64Gauge("float64.gauge")
handle := gau.Bind(labs)
mea := fix.sdk.NewFloat64Measure("float64.lastvalue")
handle := mea.Bind(labs)
b.ResetTimer()
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"
"go.opentelemetry.io/otel/sdk/metric/aggregator/array"
"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 {
@ -152,7 +152,7 @@ func TestRecordNaN(t *testing.T) {
ctx := context.Background()
batcher := &correctnessBatcher{
t: t,
agg: gauge.New(),
agg: lastvalue.New(),
}
sdk := sdk.New(batcher, sdk.NewDefaultLabelEncoder())
@ -160,10 +160,10 @@ func TestRecordNaN(t *testing.T) {
sdk.SetErrorHandler(func(handleErr error) {
sdkErr = handleErr
})
g := sdk.NewFloat64Gauge("gauge.name")
c := sdk.NewFloat64Counter("counter.name")
require.Nil(t, sdkErr)
g.Set(ctx, math.NaN(), sdk.Labels())
c.Add(ctx, math.NaN(), sdk.Labels())
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
export interfaces that support various export strategies, described below.
The metric.Meter API consists of methods for constructing each of the
basic kinds of metric instrument. There are eight types of instrument
available to the end user, comprised of four basic kinds of metric
instrument (Counter, Gauge, Measure, Observer) crossed with two kinds
of number (int64, float64).
The metric.Meter API consists of methods for constructing each of the basic
kinds of metric instrument. There are six types of instrument available to
the end user, comprised of three basic kinds of metric instrument (Counter,
Measure, Observer) crossed with two kinds of number (int64, float64).
The API assists the SDK by consolidating the variety of metric instruments
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
or a float64, depending on the instrument's definition. A single
implementation interface is used for counter, gauge and measure
instruments, metric.InstrumentImpl, and a single implementation interface
is used for their handles, metric.HandleImpl. For observers, the API
defines interfaces, for which the SDK provides an implementation.
implementation interface is used for counter and measure instruments,
metric.InstrumentImpl, and a single implementation interface is used for
their handles, metric.HandleImpl. For observers, the API defines
interfaces, for which the SDK provides an implementation.
There are four entry points for events in the Metrics API - three for
synchronous instruments (counters, gauges and measures) and one for
asynchronous instruments (observers). The entry points for synchronous
instruments are: via instrument handles, via direct instrument calls, and
via BatchRecord. The SDK is designed with handles as the primary entry
point, the other two entry points are implemented in terms of short-lived
handles. For example, the implementation of a direct call allocates a
handle, operates on the handle, and releases the handle. Similarly, the
implementation of RecordBatch uses a short-lived handle for each
measurement in the batch. The entry point for asynchronous instruments is
via observer callbacks. Observer callbacks behave like a set of instrument
handles - one for each observation for a distinct label set. The observer
handles are alive as long as they are used. If the callback stops
reporting values for a certain label set, the associated handle is dropped.
synchronous instruments (counters and measures) and one for asynchronous
instruments (observers). The entry points for synchronous instruments are:
via instrument handles, via direct instrument calls, and via BatchRecord.
The SDK is designed with handles as the primary entry point, the other two
entry points are implemented in terms of short-lived handles. For example,
the implementation of a direct call allocates a handle, operates on the
handle, and releases the handle. Similarly, the implementation of
RecordBatch uses a short-lived handle for each measurement in the batch.
The entry point for asynchronous instruments is via observer callbacks.
Observer callbacks behave like a set of instrument handles - one for each
observation for a distinct label set. The observer handles are alive as
long as they are used. If the callback stops reporting values for a
certain label set, the associated handle is dropped.
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
SDK.
Descriptor is a struct that describes the metric instrument to the
export pipeline, containing the name, recommended aggregation keys,
units, description, metric kind (counter, gauge, or measure), number
kind (int64 or float64), and whether the instrument has alternate
semantics or not (i.e., monotonic=false counter, monotonic=true gauge,
absolute=false measure). A Descriptor accompanies metric data as it
passes through the export pipeline.
Descriptor is a struct that describes the metric instrument to the export
pipeline, containing the name, recommended aggregation keys, units,
description, metric kind (counter or measure), number kind (int64 or
float64), and whether the instrument has alternate semantics or not (i.e.,
monotonic=false counter, absolute=false measure). A Descriptor accompanies
metric data as it passes through the export pipeline.
The AggregationSelector interface supports choosing the method of
aggregation to apply to a particular instrument. Given the
Descriptor, this AggregatorFor method returns an implementation of
Aggregator. If this interface returns nil, the metric will be
disabled. The aggregator should be matched to the capabilities of the
exporter. Selecting the aggregator for counter and gauge instruments
is relatively straightforward, but for measure instruments there are
numerous choices with different cost and quality tradeoffs.
aggregation to apply to a particular instrument. Given the Descriptor,
this AggregatorFor method returns an implementation of Aggregator. If this
interface returns nil, the metric will be disabled. The aggregator should
be matched to the capabilities of the exporter. Selecting the aggregator
for counter instruments is relatively straightforward, but for measure and
observer instruments there are numerous choices with different cost and
quality tradeoffs.
Aggregator is an interface which implements a concrete strategy for
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)
}
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 {
opts := api.Options{}
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...))
}
func (m *SDK) NewInt64Gauge(name string, gos ...api.GaugeOptionApplier) api.Int64Gauge {
return api.WrapInt64GaugeInstrument(m.newGaugeInstrument(name, core.Int64NumberKind, gos...))
}
func (m *SDK) NewFloat64Gauge(name string, gos ...api.GaugeOptionApplier) api.Float64Gauge {
return api.WrapFloat64GaugeInstrument(m.newGaugeInstrument(name, core.Float64NumberKind, gos...))
}
func (m *SDK) NewInt64Measure(name string, mos ...api.MeasureOptionApplier) api.Int64Measure {
return api.WrapInt64MeasureInstrument(m.newMeasureInstrument(name, core.Int64NumberKind, mos...))
}

View File

@ -19,7 +19,6 @@ import (
"go.opentelemetry.io/otel/sdk/metric/aggregator/array"
"go.opentelemetry.io/otel/sdk/metric/aggregator/counter"
"go.opentelemetry.io/otel/sdk/metric/aggregator/ddsketch"
"go.opentelemetry.io/otel/sdk/metric/aggregator/gauge"
"go.opentelemetry.io/otel/sdk/metric/aggregator/minmaxsumcount"
)
@ -38,17 +37,17 @@ var (
)
// NewWithInexpensiveMeasure returns a simple aggregation selector
// that uses counter, gauge, and minmaxsumcount aggregators for the
// four kinds of metric. This selector is faster and uses less memory
// than the others because minmaxsumcount does not aggregate quantile
// information.
// that uses counter, minmaxsumcount and minmaxsumcount aggregators
// for the three kinds of metric. This selector is faster and uses
// less memory than the others because minmaxsumcount does not
// aggregate quantile information.
func NewWithInexpensiveMeasure() export.AggregationSelector {
return selectorInexpensive{}
}
// NewWithSketchMeasure returns a simple aggregation selector that
// uses counter, gauge, and ddsketch aggregators for the four kinds of
// metric. This selector uses more cpu and memory than the
// uses counter, ddsketch, and ddsketch aggregators for the three
// kinds of metric. This selector uses more cpu and memory than the
// NewWithInexpensiveMeasure because it uses one DDSketch per distinct
// measure/observer and labelset.
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
// 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
// because it aggregates an array of all values, therefore is able to
// compute exact quantiles.
@ -68,8 +67,6 @@ func NewWithExactMeasure() export.AggregationSelector {
func (selectorInexpensive) AggregatorFor(descriptor *export.Descriptor) export.Aggregator {
switch descriptor.MetricKind() {
case export.GaugeKind:
return gauge.New()
case export.ObserverKind:
fallthrough
case export.MeasureKind:
@ -81,8 +78,6 @@ func (selectorInexpensive) AggregatorFor(descriptor *export.Descriptor) export.A
func (s selectorSketch) AggregatorFor(descriptor *export.Descriptor) export.Aggregator {
switch descriptor.MetricKind() {
case export.GaugeKind:
return gauge.New()
case export.ObserverKind:
fallthrough
case export.MeasureKind:
@ -94,8 +89,6 @@ func (s selectorSketch) AggregatorFor(descriptor *export.Descriptor) export.Aggr
func (selectorExact) AggregatorFor(descriptor *export.Descriptor) export.Aggregator {
switch descriptor.MetricKind() {
case export.GaugeKind:
return gauge.New()
case export.ObserverKind:
fallthrough
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/counter"
"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/selector/simple"
)
var (
testGaugeDesc = export.NewDescriptor("gauge", export.GaugeKind, nil, "", "", core.Int64NumberKind, false)
testCounterDesc = export.NewDescriptor("counter", export.CounterKind, nil, "", "", core.Int64NumberKind, false)
testMeasureDesc = export.NewDescriptor("measure", export.MeasureKind, nil, "", "", core.Int64NumberKind, false)
testObserverDesc = export.NewDescriptor("observer", export.ObserverKind, nil, "", "", core.Int64NumberKind, false)
@ -38,7 +36,6 @@ var (
func TestInexpensiveMeasure(t *testing.T) {
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(testMeasureDesc).(*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) {
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(testMeasureDesc).(*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) {
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(testMeasureDesc).(*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"
sdk "go.opentelemetry.io/otel/sdk/metric"
"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 (
@ -78,7 +78,7 @@ type (
newStore func() interface{}
// 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.
storeCollect func(store interface{}, value core.Number, ts time.Time)
storeExpect func(store interface{}, value core.Number)
@ -90,10 +90,10 @@ type (
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
// take the later timestamp.
gaugeState struct {
lastValueState struct {
// raw has to be aligned for 64-bit atomic operations.
raw core.Number
ts time.Time
@ -231,11 +231,12 @@ func (f *testFixture) preCollect() {
}
func (*testFixture) AggregatorFor(descriptor *export.Descriptor) export.Aggregator {
switch descriptor.MetricKind() {
case export.CounterKind:
name := descriptor.Name()
switch {
case strings.HasSuffix(name, "counter"):
return counter.New()
case export.GaugeKind:
return gauge.New()
case strings.HasSuffix(name, "lastvalue"):
return lastvalue.New()
default:
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.impl.storeCollect(actual, sum, time.Time{})
case export.GaugeKind:
gauge := agg.(aggregator.LastValue)
lv, ts, err := gauge.LastValue()
case export.MeasureKind:
lvagg := agg.(aggregator.LastValue)
lv, ts, err := lvagg.LastValue()
if err != nil && err != aggregator.ErrNoLastValue {
f.T.Fatal("Last value error: ", err)
}
@ -338,7 +339,7 @@ func float64sEqual(a, b core.Number) bool {
func intCounterTestImpl(nonMonotonic bool) testImpl {
return testImpl{
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 {
var offset int64
@ -384,7 +385,7 @@ func TestStressInt64CounterNonMonotonic(t *testing.T) {
func floatCounterTestImpl(nonMonotonic bool) testImpl {
return testImpl{
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 {
var offset float64
@ -427,34 +428,28 @@ func TestStressFloat64CounterNonMonotonic(t *testing.T) {
stressTest(t, floatCounterTestImpl(true))
}
// Gauges
func intGaugeTestImpl(monotonic bool) testImpl {
// (Now()-startTime) is used as a free monotonic source
startTime := time.Now()
// LastValue
func intLastValueTestImpl() testImpl {
return testImpl{
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 {
if !monotonic {
r1 := rand.Int63()
return core.NewInt64Number(rand.Int63() - r1)
}
return core.NewInt64Number(int64(time.Since(startTime)))
r1 := rand.Int63()
return core.NewInt64Number(rand.Int63() - r1)
},
operate: func(inst interface{}, ctx context.Context, value core.Number, labels api.LabelSet) {
gauge := inst.(api.Int64Gauge)
gauge.Set(ctx, value.AsInt64(), labels)
measure := inst.(api.Int64Measure)
measure.Record(ctx, value.AsInt64(), labels)
},
newStore: func() interface{} {
return &gaugeState{
return &lastValueState{
raw: core.NewInt64Number(0),
}
},
storeCollect: func(store interface{}, value core.Number, ts time.Time) {
gs := store.(*gaugeState)
gs := store.(*lastValueState)
if !ts.Before(gs.ts) {
gs.ts = ts
@ -462,50 +457,40 @@ func intGaugeTestImpl(monotonic bool) testImpl {
}
},
storeExpect: func(store interface{}, value core.Number) {
gs := store.(*gaugeState)
gs := store.(*lastValueState)
gs.raw.SetInt64Atomic(value.AsInt64())
},
readStore: func(store interface{}) core.Number {
gs := store.(*gaugeState)
gs := store.(*lastValueState)
return gs.raw.AsNumberAtomic()
},
equalValues: int64sEqual,
}
}
func TestStressInt64GaugeNormal(t *testing.T) {
stressTest(t, intGaugeTestImpl(false))
func TestStressInt64LastValue(t *testing.T) {
stressTest(t, intLastValueTestImpl())
}
func TestStressInt64GaugeMonotonic(t *testing.T) {
stressTest(t, intGaugeTestImpl(true))
}
func floatGaugeTestImpl(monotonic bool) testImpl {
// (Now()-startTime) is used as a free monotonic source
startTime := time.Now()
func floatLastValueTestImpl() testImpl {
return testImpl{
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 {
if !monotonic {
return core.NewFloat64Number((-0.5 + rand.Float64()) * 100000)
}
return core.NewFloat64Number(float64(time.Since(startTime)))
return core.NewFloat64Number((-0.5 + rand.Float64()) * 100000)
},
operate: func(inst interface{}, ctx context.Context, value core.Number, labels api.LabelSet) {
gauge := inst.(api.Float64Gauge)
gauge.Set(ctx, value.AsFloat64(), labels)
measure := inst.(api.Float64Measure)
measure.Record(ctx, value.AsFloat64(), labels)
},
newStore: func() interface{} {
return &gaugeState{
return &lastValueState{
raw: core.NewFloat64Number(0),
}
},
storeCollect: func(store interface{}, value core.Number, ts time.Time) {
gs := store.(*gaugeState)
gs := store.(*lastValueState)
if !ts.Before(gs.ts) {
gs.ts = ts
@ -513,21 +498,17 @@ func floatGaugeTestImpl(monotonic bool) testImpl {
}
},
storeExpect: func(store interface{}, value core.Number) {
gs := store.(*gaugeState)
gs := store.(*lastValueState)
gs.raw.SetFloat64Atomic(value.AsFloat64())
},
readStore: func(store interface{}) core.Number {
gs := store.(*gaugeState)
gs := store.(*lastValueState)
return gs.raw.AsNumberAtomic()
},
equalValues: float64sEqual,
}
}
func TestStressFloat64GaugeNormal(t *testing.T) {
stressTest(t, floatGaugeTestImpl(false))
}
func TestStressFloat64GaugeMonotonic(t *testing.T) {
stressTest(t, floatGaugeTestImpl(true))
func TestStressFloat64LastValue(t *testing.T) {
stressTest(t, floatLastValueTestImpl())
}