Description
Acknowledgements
- I have searched (https://github.com/aws/aws-sdk/issues?q=is%3Aissue) for past instances of this issue
- I have verified all of my SDK modules are up-to-date (you can perform a bulk update with
go get -u github.com/aws/aws-sdk-go-v2/...
)
Describe the bug
When supplying a MeterProvider to the s3 client, the underlying retryer seems to be creating a metrics-instrument on every API call.
Following the OTEL model (and other similar metrics-libraries) I would have expected instruments to be creating once, cached, and then getting new observations on existing instruments on each API call.
For example, from the OTEL documentation on instrumenting a Go application: https://opentelemetry.io/docs/languages/go/instrumentation/#using-counters
Click here!
import (
"net/http"
"go.opentelemetry.io/otel/metric"
)
func init() {
apiCounter, err := meter.Int64Counter(
"api.counter",
metric.WithDescription("Number of API calls."),
metric.WithUnit("{call}"),
)
if err != nil {
panic(err)
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
apiCounter.Add(r.Context(), 1)
// do some work in an API call
})
}
Excerpt of my test code:
ctx := context.Background()
cfg, err := config.LoadDefaultConfig(ctx)
if err != nil {
return fmt.Errorf("loading aws config: %s", err)
}
// setup demo meter provider (which logs calls)
s3c := s3.NewFromConfig(cfg, func(o *s3.Options) {
o.MeterProvider = &demoMeterProvider{}
})
// make a few API calls
_, err = s3c.ListBuckets(ctx, &s3.ListBucketsInput{})
if err != nil {
return fmt.Errorf("list buckets: %s", err)
}
_, err = s3c.ListBuckets(ctx, &s3.ListBucketsInput{})
if err != nil {
return fmt.Errorf("list buckets: %s", err)
}
_, err = s3c.ListBuckets(ctx, &s3.ListBucketsInput{})
if err != nil {
return fmt.Errorf("list buckets: %s", err)
}
Full code sample:
Click here!
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/smithy-go/metrics"
)
func main() {
if err := mainErr(); err != nil {
fmt.Fprintf(os.Stderr, "error: %s\n", err)
os.Exit(1)
}
}
func mainErr() error {
ctx := context.Background()
cfg, err := config.LoadDefaultConfig(ctx)
if err != nil {
return fmt.Errorf("loading aws config: %s", err)
}
// setup demo meter provider (which logs calls)
s3c := s3.NewFromConfig(cfg, func(o *s3.Options) {
o.MeterProvider = &demoMeterProvider{}
})
// make a few API calls
_, err = s3c.ListBuckets(ctx, &s3.ListBucketsInput{})
if err != nil {
return fmt.Errorf("list buckets: %s", err)
}
_, err = s3c.ListBuckets(ctx, &s3.ListBucketsInput{})
if err != nil {
return fmt.Errorf("list buckets: %s", err)
}
_, err = s3c.ListBuckets(ctx, &s3.ListBucketsInput{})
if err != nil {
return fmt.Errorf("list buckets: %s", err)
}
return nil
}
type demoMeterProvider struct{}
// Meter implements metrics.MeterProvider.
func (d *demoMeterProvider) Meter(scope string, opts ...metrics.MeterOption) metrics.Meter {
return &demoMeter{scope}
}
type demoMeter struct {
scope string
}
// Float64AsyncCounter implements metrics.Meter.
func (d *demoMeter) Float64AsyncCounter(name string, callback metrics.Float64Callback, opts ...metrics.InstrumentOption) (metrics.AsyncInstrument, error) {
d.logInstrument("Float64AsyncCounter", name)
return &demoFloatInstrument{d.scope, name}, nil
}
// Float64AsyncGauge implements metrics.Meter.
func (d *demoMeter) Float64AsyncGauge(name string, callback metrics.Float64Callback, opts ...metrics.InstrumentOption) (metrics.AsyncInstrument, error) {
d.logInstrument("Float64AsyncGauge", name)
return &demoFloatInstrument{d.scope, name}, nil
}
// Float64AsyncUpDownCounter implements metrics.Meter.
func (d *demoMeter) Float64AsyncUpDownCounter(name string, callback metrics.Float64Callback, opts ...metrics.InstrumentOption) (metrics.AsyncInstrument, error) {
d.logInstrument("Float64AsyncUpDownCounter", name)
return &demoFloatInstrument{d.scope, name}, nil
}
// Float64Counter implements metrics.Meter.
func (d *demoMeter) Float64Counter(name string, opts ...metrics.InstrumentOption) (metrics.Float64Counter, error) {
d.logInstrument("Float64Counter", name)
return &demoFloatInstrument{d.scope, name}, nil
}
// Float64Gauge implements metrics.Meter.
func (d *demoMeter) Float64Gauge(name string, opts ...metrics.InstrumentOption) (metrics.Float64Gauge, error) {
d.logInstrument("Float64Gauge", name)
return &demoFloatInstrument{d.scope, name}, nil
}
// Float64Histogram implements metrics.Meter.
func (d *demoMeter) Float64Histogram(name string, opts ...metrics.InstrumentOption) (metrics.Float64Histogram, error) {
d.logInstrument("Float64Histogram", name)
return &demoFloatInstrument{d.scope, name}, nil
}
// Float64UpDownCounter implements metrics.Meter.
func (d *demoMeter) Float64UpDownCounter(name string, opts ...metrics.InstrumentOption) (metrics.Float64UpDownCounter, error) {
d.logInstrument("Float64UpDownCounter", name)
return &demoFloatInstrument{d.scope, name}, nil
}
// Int64AsyncCounter implements metrics.Meter.
func (d *demoMeter) Int64AsyncCounter(name string, callback metrics.Int64Callback, opts ...metrics.InstrumentOption) (metrics.AsyncInstrument, error) {
d.logInstrument("Int64AsyncCounter", name)
return &demoIntInstrument{d.scope, name}, nil
}
// Int64AsyncGauge implements metrics.Meter.
func (d *demoMeter) Int64AsyncGauge(name string, callback metrics.Int64Callback, opts ...metrics.InstrumentOption) (metrics.AsyncInstrument, error) {
d.logInstrument("Int64AsyncGauge", name)
return &demoIntInstrument{d.scope, name}, nil
}
// Int64AsyncUpDownCounter implements metrics.Meter.
func (d *demoMeter) Int64AsyncUpDownCounter(name string, callback metrics.Int64Callback, opts ...metrics.InstrumentOption) (metrics.AsyncInstrument, error) {
d.logInstrument("Int64AsyncUpDownCounter", name)
return &demoIntInstrument{d.scope, name}, nil
}
// Int64Counter implements metrics.Meter.
func (d *demoMeter) Int64Counter(name string, opts ...metrics.InstrumentOption) (metrics.Int64Counter, error) {
d.logInstrument("Int64Counter", name)
return &demoIntInstrument{d.scope, name}, nil
}
// Int64Gauge implements metrics.Meter.
func (d *demoMeter) Int64Gauge(name string, opts ...metrics.InstrumentOption) (metrics.Int64Gauge, error) {
d.logInstrument("Int64Gauge", name)
return &demoIntInstrument{d.scope, name}, nil
}
// Int64Histogram implements metrics.Meter.
func (d *demoMeter) Int64Histogram(name string, opts ...metrics.InstrumentOption) (metrics.Int64Histogram, error) {
d.logInstrument("Int64Histogram", name)
return &demoIntInstrument{d.scope, name}, nil
}
// Int64UpDownCounter implements metrics.Meter.
func (d *demoMeter) Int64UpDownCounter(name string, opts ...metrics.InstrumentOption) (metrics.Int64UpDownCounter, error) {
d.logInstrument("Int64UpDownCounter", name)
return &demoIntInstrument{d.scope, name}, nil
}
func (d *demoMeter) logInstrument(typ, name string) {
log.Printf("creating %s instrument: %s", typ, name)
}
type demoFloatInstrument struct {
scope string
name string
}
// Record implements metrics.Float64Histogram.
func (d *demoFloatInstrument) Record(context.Context, float64, ...metrics.RecordMetricOption) {
log.Printf("%s Record", d.name)
}
// Sample implements metrics.Float64Gauge.
func (d *demoFloatInstrument) Sample(context.Context, float64, ...metrics.RecordMetricOption) {
log.Printf("%s Sample", d.name)
}
// Add implements metrics.Float64Counter.
func (d *demoFloatInstrument) Add(context.Context, float64, ...metrics.RecordMetricOption) {
log.Printf("%s Add", d.name)
}
// Stop implements metrics.AsyncInstrument.
func (d *demoFloatInstrument) Stop() {
log.Printf("%s Stop", d.name)
}
type demoIntInstrument struct {
scope string
name string
}
// Record implements metrics.Int64Histogram.
func (d *demoIntInstrument) Record(context.Context, int64, ...metrics.RecordMetricOption) {
log.Printf("%s Record", d.name)
}
// Sample implements metrics.Int64Gauge.
func (d *demoIntInstrument) Sample(context.Context, int64, ...metrics.RecordMetricOption) {
log.Printf("%s Sample", d.name)
}
// Add implements metrics.Int64Counter.
func (d *demoIntInstrument) Add(context.Context, int64, ...metrics.RecordMetricOption) {
log.Printf("%s Add", d.name)
}
// Stop implements metrics.AsyncInstrument.
func (d *demoIntInstrument) Stop() {
log.Printf("%s Stop", d.name)
}
Regression Issue
- Select this option if this issue appears to be a regression.
Expected Behavior
I expect my demo-meter-provider to log each instrument creation only once.
Current Behavior
The instruments were creating three times - once per API call I made to the S3 service:
Click here!
2024/10/04 13:32:26 creating Float64Histogram instrument: client.call.duration
2024/10/04 13:32:26 creating Float64Histogram instrument: client.call.serialization_duration
2024/10/04 13:32:26 creating Float64Histogram instrument: client.call.auth.resolve_identity_duration
2024/10/04 13:32:26 creating Float64Histogram instrument: client.call.resolve_endpoint_duration
2024/10/04 13:32:26 creating Float64Histogram instrument: client.call.auth.signing_duration
2024/10/04 13:32:26 creating Float64Histogram instrument: client.call.deserialization_duration
2024/10/04 13:32:26 client.call.serialization_duration Record
2024/10/04 13:32:27 client.call.auth.resolve_identity_duration Record
2024/10/04 13:32:27 client.call.resolve_endpoint_duration Record
2024/10/04 13:32:27 creating Int64Counter instrument: client.call.attempts
2024/10/04 13:32:27 creating Int64Counter instrument: client.call.errors
2024/10/04 13:32:27 creating Float64Histogram instrument: client.call.attempt_duration
2024/10/04 13:32:27 client.call.attempts Add
2024/10/04 13:32:27 client.call.auth.signing_duration Record
2024/10/04 13:32:27 client.call.deserialization_duration Record
2024/10/04 13:32:27 client.call.attempt_duration Record
2024/10/04 13:32:27 client.call.duration Record
2024/10/04 13:32:27 creating Float64Histogram instrument: client.call.duration
2024/10/04 13:32:27 creating Float64Histogram instrument: client.call.serialization_duration
2024/10/04 13:32:27 creating Float64Histogram instrument: client.call.auth.resolve_identity_duration
2024/10/04 13:32:27 creating Float64Histogram instrument: client.call.resolve_endpoint_duration
2024/10/04 13:32:27 creating Float64Histogram instrument: client.call.auth.signing_duration
2024/10/04 13:32:27 creating Float64Histogram instrument: client.call.deserialization_duration
2024/10/04 13:32:27 client.call.serialization_duration Record
2024/10/04 13:32:27 client.call.auth.resolve_identity_duration Record
2024/10/04 13:32:27 client.call.resolve_endpoint_duration Record
2024/10/04 13:32:27 creating Int64Counter instrument: client.call.attempts
2024/10/04 13:32:27 creating Int64Counter instrument: client.call.errors
2024/10/04 13:32:27 creating Float64Histogram instrument: client.call.attempt_duration
2024/10/04 13:32:27 client.call.attempts Add
2024/10/04 13:32:27 client.call.auth.signing_duration Record
2024/10/04 13:32:27 client.call.deserialization_duration Record
2024/10/04 13:32:27 client.call.attempt_duration Record
2024/10/04 13:32:27 client.call.duration Record
2024/10/04 13:32:27 creating Float64Histogram instrument: client.call.duration
2024/10/04 13:32:27 creating Float64Histogram instrument: client.call.serialization_duration
2024/10/04 13:32:27 creating Float64Histogram instrument: client.call.auth.resolve_identity_duration
2024/10/04 13:32:27 creating Float64Histogram instrument: client.call.resolve_endpoint_duration
2024/10/04 13:32:27 creating Float64Histogram instrument: client.call.auth.signing_duration
2024/10/04 13:32:27 creating Float64Histogram instrument: client.call.deserialization_duration
2024/10/04 13:32:27 client.call.serialization_duration Record
2024/10/04 13:32:27 client.call.auth.resolve_identity_duration Record
2024/10/04 13:32:27 client.call.resolve_endpoint_duration Record
2024/10/04 13:32:27 creating Int64Counter instrument: client.call.attempts
2024/10/04 13:32:27 creating Int64Counter instrument: client.call.errors
2024/10/04 13:32:27 creating Float64Histogram instrument: client.call.attempt_duration
2024/10/04 13:32:27 client.call.attempts Add
2024/10/04 13:32:27 client.call.auth.signing_duration Record
2024/10/04 13:32:27 client.call.deserialization_duration Record
2024/10/04 13:32:27 client.call.attempt_duration Record
2024/10/04 13:32:27 client.call.duration Record
Reproduction Steps
$ AWS_REGION=us-east-1 go run ./
Possible Solution
No response
Additional Information/Context
No response
AWS Go SDK V2 Module Versions Used
$ grep github.com/aws/ go.mod
github.com/aws/aws-sdk-go-v2/config v1.27.40
github.com/aws/aws-sdk-go-v2/service/s3 v1.64.1
github.com/aws/aws-sdk-go-v2 v1.31.0 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.5 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.38 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.18 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.20 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.18 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.23.4 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.4 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.31.4 // indirect
github.com/aws/smithy-go v1.21.0 // indirect
Compiler and Version used
go version go1.23.1 linux/amd64
Operating System and version
Arch Linux x86_64, Linux 6.10.10-arch1-1