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

Support OTEL_METRIC_EXPORT_INTERVAL and OTEL_METRIC_EXPORT_TIMEOUT #3763

Merged
merged 8 commits into from
Feb 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Add `bridgetSpanContext.IsSampled` to `go.opentelemetry.io/otel/bridget/opentracing` to expose whether span is sampled or not. (#3570)
- The `WithInstrumentationAttributes` option to `go.opentelemetry.io/otel/metric`. (#3738)
- The `WithInstrumentationAttributes` option to `go.opentelemetry.io/otel/trace`. (#3739)
- The following environment variables are supported by the `Reader`s in `go.opentelemetry.io/otel/sdk/metric`. (#3763)
- `OTEL_METRIC_EXPORT_INTERVAL`
- `OTEL_METRIC_EXPORT_TIMEOUT`

### Changed

Expand Down
50 changes: 50 additions & 0 deletions sdk/metric/env.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// 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 "go.opentelemetry.io/otel/sdk/metric"

import (
"os"
"strconv"
"time"

"go.opentelemetry.io/otel/internal/global"
)

// Environment variable names.
const (
// The time interval (in milliseconds) between the start of two export attempts.
envInterval = "OTEL_METRIC_EXPORT_INTERVAL"
// Maximum allowed time (in milliseconds) to export data.
envTimeout = "OTEL_METRIC_EXPORT_TIMEOUT"
)

// envDuration returns an environment variable's value as duration in milliseconds if it is exists,
// or the defaultValue if the environment variable is not defined or the value is not valid.
func envDuration(key string, defaultValue time.Duration) time.Duration {
v := os.Getenv(key)
if v == "" {
return defaultValue
}
d, err := strconv.Atoi(v)
if err != nil {
global.Error(err, "parse duration", "environment variable", key, "value", v)
return defaultValue
}
if d <= 0 {
global.Error(errNonPositiveDuration, "non-positive duration", "environment variable", key, "value", v)
return defaultValue
}
return time.Duration(d) * time.Millisecond
}
10 changes: 8 additions & 2 deletions sdk/metric/periodic_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ type periodicReaderConfig struct {
// options.
func newPeriodicReaderConfig(options []PeriodicReaderOption) periodicReaderConfig {
c := periodicReaderConfig{
interval: defaultInterval,
timeout: defaultTimeout,
interval: envDuration(envInterval, defaultInterval),
timeout: envDuration(envTimeout, defaultTimeout),
}
for _, o := range options {
c = o.applyPeriodic(c)
Expand All @@ -69,6 +69,9 @@ func (o periodicReaderOptionFunc) applyPeriodic(conf periodicReaderConfig) perio
// WithTimeout configures the time a PeriodicReader waits for an export to
// complete before canceling it.
//
// This option overrides any value set for the
// OTEL_METRIC_EXPORT_TIMEOUT environment variable.
//
// If this option is not used or d is less than or equal to zero, 30 seconds
// is used as the default.
func WithTimeout(d time.Duration) PeriodicReaderOption {
Expand All @@ -84,6 +87,9 @@ func WithTimeout(d time.Duration) PeriodicReaderOption {
// WithInterval configures the intervening time between exports for a
// PeriodicReader.
//
// This option overrides any value set for the
// OTEL_METRIC_EXPORT_INTERVAL environment variable.
//
// If this option is not used or d is less than or equal to zero, 60 seconds
// is used as the default.
func WithInterval(d time.Duration) PeriodicReaderOption {
Expand Down
96 changes: 96 additions & 0 deletions sdk/metric/periodic_reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,54 @@ func TestWithTimeout(t *testing.T) {
assert.Equal(t, defaultTimeout, test(time.Duration(-1)), "invalid timeout should use default")
}

func TestTimeoutEnvVar(t *testing.T) {
testCases := []struct {
v string
want time.Duration
}{
{
// empty value
"",
defaultTimeout,
},
{
// positive value
"1",
time.Millisecond,
},
{
// non-positive value
"0",
defaultTimeout,
},
{
// value with unit (not supported)
"1ms",
defaultTimeout,
},
{
// NaN
"abc",
defaultTimeout,
},
}
for _, tc := range testCases {
t.Run(tc.v, func(t *testing.T) {
t.Setenv(envTimeout, tc.v)
got := newPeriodicReaderConfig(nil).timeout
assert.Equal(t, tc.want, got)
})
}
}

func TestTimeoutEnvAndOption(t *testing.T) {
want := 5 * time.Millisecond
t.Setenv(envTimeout, "999")
opts := []PeriodicReaderOption{WithTimeout(want)}
got := newPeriodicReaderConfig(opts).timeout
assert.Equal(t, want, got, "option should have precedence over env var")
}

func TestWithInterval(t *testing.T) {
test := func(d time.Duration) time.Duration {
opts := []PeriodicReaderOption{WithInterval(d)}
Expand All @@ -53,6 +101,54 @@ func TestWithInterval(t *testing.T) {
assert.Equal(t, defaultInterval, test(time.Duration(-1)), "invalid interval should use default")
}

func TestIntervalEnvVar(t *testing.T) {
testCases := []struct {
v string
want time.Duration
}{
{
// empty value
"",
defaultInterval,
},
{
// positive value
"1",
time.Millisecond,
},
{
// non-positive value
"0",
defaultInterval,
},
{
// value with unit (not supported)
"1ms",
defaultInterval,
},
{
// NaN
"abc",
defaultInterval,
},
}
for _, tc := range testCases {
t.Run(tc.v, func(t *testing.T) {
t.Setenv(envInterval, tc.v)
got := newPeriodicReaderConfig(nil).interval
assert.Equal(t, tc.want, got)
})
}
}

func TestIntervalEnvAndOption(t *testing.T) {
want := 5 * time.Millisecond
t.Setenv(envInterval, "999")
opts := []PeriodicReaderOption{WithInterval(want)}
got := newPeriodicReaderConfig(opts).interval
assert.Equal(t, want, got, "option should have precedence over env var")
}

type fnExporter struct {
temporalityFunc TemporalitySelector
aggregationFunc AggregationSelector
Expand Down
4 changes: 4 additions & 0 deletions sdk/metric/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ var ErrReaderNotRegistered = fmt.Errorf("reader is not registered")
// reader has been Shutdown once.
var ErrReaderShutdown = fmt.Errorf("reader is shutdown")

// errNonPositiveDuration is logged when an environmental variable
// has non-positive value.
var errNonPositiveDuration = fmt.Errorf("non-positive duration")

// Reader is the interface used between the SDK and an
// exporter. Control flow is bi-directional through the
// Reader, since the SDK initiates ForceFlush and Shutdown
Expand Down