Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the Asynchronous and Synchronous state packages for review LS-29754 #173

Merged
merged 10 commits into from
Jun 7, 2022
59 changes: 59 additions & 0 deletions lightstep/sdk/metric/asyncinst.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright The 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 "github.com/lightstep/otel-launcher-go/lightstep/sdk/metric"

import (
"go.opentelemetry.io/otel/metric/instrument"
"go.opentelemetry.io/otel/metric/instrument/asyncfloat64"
"go.opentelemetry.io/otel/metric/instrument/asyncint64"
"github.com/lightstep/otel-launcher-go/lightstep/sdk/metric/internal/asyncstate"
"github.com/lightstep/otel-launcher-go/lightstep/sdk/metric/number"
"github.com/lightstep/otel-launcher-go/lightstep/sdk/metric/sdkinstrument"
)

type (
asyncint64Instruments struct{ *meter }
asyncfloat64Instruments struct{ *meter }
)

func (i asyncint64Instruments) Counter(name string, opts ...instrument.Option) (asyncint64.Counter, error) {
inst, err := i.asynchronousInstrument(name, opts, number.Int64Kind, sdkinstrument.CounterObserverKind)
return asyncstate.NewObserver[int64, number.Int64Traits](inst), err
}

func (i asyncint64Instruments) UpDownCounter(name string, opts ...instrument.Option) (asyncint64.UpDownCounter, error) {
inst, err := i.asynchronousInstrument(name, opts, number.Int64Kind, sdkinstrument.UpDownCounterObserverKind)
return asyncstate.NewObserver[int64, number.Int64Traits](inst), err
}

func (i asyncint64Instruments) Gauge(name string, opts ...instrument.Option) (asyncint64.Gauge, error) {
inst, err := i.asynchronousInstrument(name, opts, number.Int64Kind, sdkinstrument.GaugeObserverKind)
return asyncstate.NewObserver[int64, number.Int64Traits](inst), err
}

func (f asyncfloat64Instruments) Counter(name string, opts ...instrument.Option) (asyncfloat64.Counter, error) {
inst, err := f.asynchronousInstrument(name, opts, number.Float64Kind, sdkinstrument.CounterObserverKind)
return asyncstate.NewObserver[float64, number.Float64Traits](inst), err
}

func (f asyncfloat64Instruments) UpDownCounter(name string, opts ...instrument.Option) (asyncfloat64.UpDownCounter, error) {
inst, err := f.asynchronousInstrument(name, opts, number.Float64Kind, sdkinstrument.UpDownCounterObserverKind)
return asyncstate.NewObserver[float64, number.Float64Traits](inst), err
}

func (f asyncfloat64Instruments) Gauge(name string, opts ...instrument.Option) (asyncfloat64.Gauge, error) {
inst, err := f.asynchronousInstrument(name, opts, number.Float64Kind, sdkinstrument.GaugeObserverKind)
return asyncstate.NewObserver[float64, number.Float64Traits](inst), err
}
92 changes: 92 additions & 0 deletions lightstep/sdk/metric/asyncinst_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright The 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"
"testing"
"time"

"github.com/lightstep/otel-launcher-go/lightstep/sdk/metric/aggregator/aggregation"
"github.com/lightstep/otel-launcher-go/lightstep/sdk/metric/aggregator/gauge"
"github.com/lightstep/otel-launcher-go/lightstep/sdk/metric/aggregator/sum"
"github.com/lightstep/otel-launcher-go/lightstep/sdk/metric/internal/test"
"github.com/lightstep/otel-launcher-go/lightstep/sdk/metric/number"
"github.com/lightstep/otel-launcher-go/lightstep/sdk/metric/sdkinstrument"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric/instrument"
"go.opentelemetry.io/otel/sdk/resource"
)

func TestAsyncInsts(t *testing.T) {
rdr := NewManualReader("test")
res := resource.Empty()
provider := NewMeterProvider(WithReader(rdr), WithResource(res))

ci := must(provider.Meter("test").AsyncInt64().Counter("icount"))
cf := must(provider.Meter("test").AsyncFloat64().Counter("fcount"))
ui := must(provider.Meter("test").AsyncInt64().UpDownCounter("iupcount"))
uf := must(provider.Meter("test").AsyncFloat64().UpDownCounter("fupcount"))
gi := must(provider.Meter("test").AsyncInt64().Gauge("igauge"))
gf := must(provider.Meter("test").AsyncFloat64().Gauge("fgauge"))

attr := attribute.String("a", "B")

_ = provider.Meter("test").RegisterCallback([]instrument.Asynchronous{
ci, cf, ui, uf, gi, gf,
}, func(ctx context.Context) {
ci.Observe(ctx, 2, attr)
cf.Observe(ctx, 3, attr)
ui.Observe(ctx, 4, attr)
uf.Observe(ctx, 5, attr)
gi.Observe(ctx, 6, attr)
gf.Observe(ctx, 7, attr)
})

data := rdr.Produce(nil)
notime := time.Time{}
cumulative := aggregation.CumulativeTemporality

test.RequireEqualResourceMetrics(
t, data, res,
test.Scope(
test.Library("test"),
test.Instrument(
test.Descriptor("icount", sdkinstrument.CounterObserverKind, number.Int64Kind),
test.Point(notime, notime, sum.NewMonotonicInt64(2), cumulative, attr),
),
test.Instrument(
test.Descriptor("fcount", sdkinstrument.CounterObserverKind, number.Float64Kind),
test.Point(notime, notime, sum.NewMonotonicFloat64(3), cumulative, attr),
),
test.Instrument(
test.Descriptor("iupcount", sdkinstrument.UpDownCounterObserverKind, number.Int64Kind),
test.Point(notime, notime, sum.NewNonMonotonicInt64(4), cumulative, attr),
),
test.Instrument(
test.Descriptor("fupcount", sdkinstrument.UpDownCounterObserverKind, number.Float64Kind),
test.Point(notime, notime, sum.NewNonMonotonicFloat64(5), cumulative, attr),
),
test.Instrument(
test.Descriptor("igauge", sdkinstrument.GaugeObserverKind, number.Int64Kind),
test.Point(notime, notime, gauge.NewInt64(6), cumulative, attr),
),
test.Instrument(
test.Descriptor("fgauge", sdkinstrument.GaugeObserverKind, number.Float64Kind),
test.Point(notime, notime, gauge.NewFloat64(7), cumulative, attr),
),
),
)
}
147 changes: 147 additions & 0 deletions lightstep/sdk/metric/benchmark_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// Copyright The 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 "github.com/lightstep/otel-launcher-go/lightstep/sdk/metric"

import (
"context"
"testing"

"go.opentelemetry.io/otel/attribute"
"github.com/lightstep/otel-launcher-go/lightstep/sdk/metric/data"
)

func BenchmarkCounterAddNoAttrs(b *testing.B) {
ctx := context.Background()
rdr := NewManualReader("bench")
provider := NewMeterProvider(WithReader(rdr))
b.ReportAllocs()

cntr, _ := provider.Meter("test").SyncInt64().Counter("hello")

for i := 0; i < b.N; i++ {
cntr.Add(ctx, 1)
}
}

// Benchmark prints 3 allocs per Add():
// 1. new []attribute.KeyValue for the list of attributes
// 2. interface{} wrapper around attribute.Set
// 3. an attribute array (map key)
func BenchmarkCounterAddOneAttr(b *testing.B) {
ctx := context.Background()
rdr := NewManualReader("bench")
provider := NewMeterProvider(WithReader(rdr))
b.ReportAllocs()

cntr, _ := provider.Meter("test").SyncInt64().Counter("hello")

for i := 0; i < b.N; i++ {
cntr.Add(ctx, 1, attribute.String("K", "V"))
}
}

// Benchmark prints 11 allocs per Add(), I see 10 in the profile:
// 1. new []attribute.KeyValue for the list of attributes
// 2. an attribute.Sortable (acquireRecord)
// 3. the attribute.Set underlying array
// 4. interface{} wrapper around attribute.Set value
// 5. internal to sync.Map
// 6. internal sync.Map
// 7. new syncstate.record
// 8. new viewstate.syncAccumulator
// 9. an attribute.Sortable (findOutput)
// 10. an output Aggregator
func BenchmarkCounterAddManyAttrs(b *testing.B) {
ctx := context.Background()
rdr := NewManualReader("bench")
provider := NewMeterProvider(WithReader(rdr))
b.ReportAllocs()

cntr, _ := provider.Meter("test").SyncInt64().Counter("hello")

for i := 0; i < b.N; i++ {
cntr.Add(ctx, 1, attribute.Int("K", i))
}
}

func BenchmarkCounterCollectOneAttrNoReuse(b *testing.B) {
ctx := context.Background()
rdr := NewManualReader("bench")
provider := NewMeterProvider(WithReader(rdr))
b.ReportAllocs()

cntr, _ := provider.Meter("test").SyncInt64().Counter("hello")

for i := 0; i < b.N; i++ {
cntr.Add(ctx, 1, attribute.Int("K", 1))

_ = rdr.Produce(nil)
}
}

func BenchmarkCounterCollectOneAttrWithReuse(b *testing.B) {
ctx := context.Background()
rdr := NewManualReader("bench")
provider := NewMeterProvider(WithReader(rdr))
b.ReportAllocs()

cntr, _ := provider.Meter("test").SyncInt64().Counter("hello")

var reuse data.Metrics

for i := 0; i < b.N; i++ {
cntr.Add(ctx, 1, attribute.Int("K", 1))

reuse = rdr.Produce(&reuse)
}
}

func BenchmarkCounterCollectTenAttrs(b *testing.B) {
ctx := context.Background()
rdr := NewManualReader("bench")
provider := NewMeterProvider(WithReader(rdr))
b.ReportAllocs()

cntr, _ := provider.Meter("test").SyncInt64().Counter("hello")

var reuse data.Metrics

for i := 0; i < b.N; i++ {
for j := 0; j < 10; j++ {
cntr.Add(ctx, 1, attribute.Int("K", j))
}
reuse = rdr.Produce(&reuse)
}
}

func BenchmarkCounterCollectTenAttrsTenTimes(b *testing.B) {
ctx := context.Background()
rdr := NewManualReader("bench")
provider := NewMeterProvider(WithReader(rdr))
b.ReportAllocs()

cntr, _ := provider.Meter("test").SyncInt64().Counter("hello")

var reuse data.Metrics

for i := 0; i < b.N; i++ {
for k := 0; k < 10; k++ {
for j := 0; j < 10; j++ {
cntr.Add(ctx, 1, attribute.Int("K", j))
}
reuse = rdr.Produce(&reuse)
}
}
}
65 changes: 65 additions & 0 deletions lightstep/sdk/metric/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright The 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 "github.com/lightstep/otel-launcher-go/lightstep/sdk/metric"

import (
"github.com/lightstep/otel-launcher-go/lightstep/sdk/metric/view"
"go.opentelemetry.io/otel/sdk/resource"
)

// config contains configuration options for a MeterProvider.
type config struct {
// res is the resource for this MeterProvider.
res *resource.Resource

// readers is a slice of Reader instances corresponding with views.
// the i'th reader uses the i'th entry in views.
readers []Reader

// views is a slice of *Views instances corresponding with readers.
// the i'th views applies to the i'th reader.
views []*view.Views
}

// Option applies a configuration option value to a MeterProvider.
type Option interface {
apply(config) config
}

// optionFunction makes a functional Option out of a function object.
type optionFunction func(cfg config) config

// apply implements Option.
func (of optionFunction) apply(in config) config {
return of(in)
}

// WithResource associates a Resource with a new MeterProvider.
func WithResource(res *resource.Resource) Option {
return optionFunction(func(cfg config) config {
cfg.res = res
return cfg
})
}

// WithReader associates a new Reader and associated View options with
// a new MeterProvider
func WithReader(r Reader, opts ...view.Option) Option {
return optionFunction(func(cfg config) config {
cfg.readers = append(cfg.readers, r)
cfg.views = append(cfg.views, view.New(r.String(), opts...))
return cfg
})
}
Loading