diff --git a/internal/env.go b/internal/env.go index 7c4ed6c0cd..ae377ecbc3 100644 --- a/internal/env.go +++ b/internal/env.go @@ -92,3 +92,18 @@ func ParseTagString(str string) map[string]string { ForEachStringTag(str, func(key, val string) { res[key] = val }) return res } + +// FloatEnv returns the parsed float64 value of an environment variable, +// or def otherwise. +func FloatEnv(key string, def float64) float64 { + env, ok := os.LookupEnv(key) + if !ok { + return def + } + v, err := strconv.ParseFloat(env, 64) + if err != nil { + log.Warn("Non-float value for env var %s, defaulting to %f. Parse failed with error: %v", key, def, err) + return def + } + return v +} diff --git a/internal/remoteconfig/config.go b/internal/remoteconfig/config.go index 3d2b8ed631..45fd99e02a 100644 --- a/internal/remoteconfig/config.go +++ b/internal/remoteconfig/config.go @@ -56,14 +56,13 @@ func DefaultClientConfig() ClientConfig { } func pollIntervalFromEnv() time.Duration { - interval := internal.IntEnv(envPollIntervalSec, 5) + interval := internal.FloatEnv(envPollIntervalSec, 5.0) if interval < 0 { - log.Debug("Remote config: cannot use a negative poll interval: %s = %d. Defaulting to 5s.", envPollIntervalSec, interval) - return 5 * time.Second + log.Debug("Remote config: cannot use a negative poll interval: %s = %f. Defaulting to 5s.", envPollIntervalSec, interval) + interval = 5.0 } else if interval == 0 { log.Debug("Remote config: poll interval set to 0. Polling will be continuous.") return time.Nanosecond } - - return time.Duration(interval) * time.Second + return time.Duration(interval * float64(time.Second)) } diff --git a/internal/remoteconfig/config_test.go b/internal/remoteconfig/config_test.go new file mode 100644 index 0000000000..803f96288f --- /dev/null +++ b/internal/remoteconfig/config_test.go @@ -0,0 +1,54 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2023 Datadog, Inc. + +package remoteconfig + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func Test_pollIntervalFromEnv(t *testing.T) { + defaultInterval := time.Second * time.Duration(5.0) + tests := []struct { + name string + setup func(t *testing.T) + want time.Duration + }{ + { + name: "default", + setup: func(t *testing.T) {}, + want: defaultInterval, + }, + { + name: "float", + setup: func(t *testing.T) { t.Setenv("DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS", "0.2") }, + want: time.Millisecond * 200, + }, + { + name: "integer", + setup: func(t *testing.T) { t.Setenv("DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS", "2") }, + want: time.Second * 2, + }, + { + name: "negative", + setup: func(t *testing.T) { t.Setenv("DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS", "-1") }, + want: defaultInterval, + }, + { + name: "zero", + setup: func(t *testing.T) { t.Setenv("DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS", "0") }, + want: time.Nanosecond, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.setup(t) + assert.Equal(t, tt.want, pollIntervalFromEnv()) + }) + } +} diff --git a/internal/telemetry/client.go b/internal/telemetry/client.go index 6b32dec76b..c2c07d62be 100644 --- a/internal/telemetry/client.go +++ b/internal/telemetry/client.go @@ -76,7 +76,7 @@ var ( // also the default URL in case connecting to the agent URL fails. agentlessURL = "https://instrumentation-telemetry-intake.datadoghq.com/api/v2/apmtelemetry" - defaultHeartbeatInterval = 60 // seconds + defaultHeartbeatInterval = 60.0 // seconds // LogPrefix specifies the prefix for all telemetry logging LogPrefix = "Instrumentation telemetry: " @@ -222,14 +222,17 @@ func (c *client) start(configuration []Configuration, namespace Namespace) { } c.flush() + c.heartbeatInterval = heartbeatInterval() + c.heartbeatT = time.AfterFunc(c.heartbeatInterval, c.backgroundHeartbeat) +} - heartbeat := internal.IntEnv("DD_TELEMETRY_HEARTBEAT_INTERVAL", defaultHeartbeatInterval) - if heartbeat < 1 || heartbeat > 3600 { - log("DD_TELEMETRY_HEARTBEAT_INTERVAL=%d not in [1,3600] range, setting to default of %d", heartbeat, defaultHeartbeatInterval) +func heartbeatInterval() time.Duration { + heartbeat := internal.FloatEnv("DD_TELEMETRY_HEARTBEAT_INTERVAL", defaultHeartbeatInterval) + if heartbeat <= 0 || heartbeat > 3600 { + log("DD_TELEMETRY_HEARTBEAT_INTERVAL=%d not in [1,3600] range, setting to default of %f", heartbeat, defaultHeartbeatInterval) heartbeat = defaultHeartbeatInterval } - c.heartbeatInterval = time.Duration(heartbeat) * time.Second - c.heartbeatT = time.AfterFunc(c.heartbeatInterval, c.backgroundHeartbeat) + return time.Duration(heartbeat * float64(time.Second)) } // Stop notifies the telemetry endpoint that the app is closing. All outstanding diff --git a/internal/telemetry/client_test.go b/internal/telemetry/client_test.go index da3ee137e0..7c62d90503 100644 --- a/internal/telemetry/client_test.go +++ b/internal/telemetry/client_test.go @@ -15,6 +15,8 @@ import ( "sync" "testing" "time" + + "github.com/stretchr/testify/assert" ) func TestClient(t *testing.T) { @@ -387,3 +389,49 @@ func TestCollectDependencies(t *testing.T) { t.Fatalf("Timed out waiting for dependency payload") } } + +func Test_heartbeatInterval(t *testing.T) { + defaultInterval := time.Second * time.Duration(defaultHeartbeatInterval) + tests := []struct { + name string + setup func(t *testing.T) + want time.Duration + }{ + { + name: "default", + setup: func(t *testing.T) {}, + want: defaultInterval, + }, + { + name: "float", + setup: func(t *testing.T) { t.Setenv("DD_TELEMETRY_HEARTBEAT_INTERVAL", "0.2") }, + want: time.Millisecond * 200, + }, + { + name: "integer", + setup: func(t *testing.T) { t.Setenv("DD_TELEMETRY_HEARTBEAT_INTERVAL", "2") }, + want: time.Second * 2, + }, + { + name: "negative", + setup: func(t *testing.T) { t.Setenv("DD_TELEMETRY_HEARTBEAT_INTERVAL", "-1") }, + want: defaultInterval, + }, + { + name: "zero", + setup: func(t *testing.T) { t.Setenv("DD_TELEMETRY_HEARTBEAT_INTERVAL", "0") }, + want: defaultInterval, + }, + { + name: "long", + setup: func(t *testing.T) { t.Setenv("DD_TELEMETRY_HEARTBEAT_INTERVAL", "4000") }, + want: defaultInterval, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.setup(t) + assert.Equal(t, tt.want, heartbeatInterval()) + }) + } +}