diff --git a/cmd/windows_exporter/main.go b/cmd/windows_exporter/main.go index 5159d585..fa20d3c0 100644 --- a/cmd/windows_exporter/main.go +++ b/cmd/windows_exporter/main.go @@ -25,6 +25,7 @@ import ( "fmt" "log/slog" "maps" + "math" "net/http" "net/http/pprof" "os" @@ -127,7 +128,7 @@ func run(ctx context.Context, args []string) int { ).Default("normal").String() memoryLimit = app.Flag( "process.memory-limit", - "Limit memory usage in bytes. This is a soft-limit and not guaranteed. 0 means no limit. Read more at https://pkg.go.dev/runtime/debug#SetMemoryLimit .", + "Limit memory usage in bytes. This is a soft-limit and not guaranteed. 0 means no limit. Negative values are invalid. Read more at https://pkg.go.dev/runtime/debug#SetMemoryLimit .", ).Default("200000000").Int64() ) @@ -153,7 +154,11 @@ func run(ctx context.Context, args []string) int { return 1 } - debug.SetMemoryLimit(*memoryLimit) + if err := setProcessMemoryLimit(*memoryLimit); err != nil { + logStartupError(ctx, "Invalid process memory limit", err) + + return 1 + } logger, err := log.New(logConfig) if err != nil { @@ -400,3 +405,18 @@ func expandEnabledCollectors(enabled string) []string { return slices.Compact(strings.Split(expanded, ",")) } + +// setProcessMemoryLimit applies the CLI contract: zero disables the soft limit. +func setProcessMemoryLimit(limit int64) error { + if limit < 0 { + return errors.New("process.memory-limit must be non-negative") + } + + if limit == 0 { + limit = math.MaxInt64 + } + + debug.SetMemoryLimit(limit) + + return nil +} diff --git a/cmd/windows_exporter/memory_limit_test.go b/cmd/windows_exporter/memory_limit_test.go new file mode 100644 index 00000000..6da798a3 --- /dev/null +++ b/cmd/windows_exporter/memory_limit_test.go @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright The Prometheus 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. + +//go:build windows + +package main + +import ( + "math" + "runtime/debug" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestProcessMemoryLimit(t *testing.T) { + // This setting is process-wide, so the cases must run sequentially. + previous := debug.SetMemoryLimit(-1) + + t.Cleanup(func() { debug.SetMemoryLimit(previous) }) + + require.NoError(t, setProcessMemoryLimit(0)) + require.Equal(t, int64(math.MaxInt64), debug.SetMemoryLimit(-1)) + require.NoError(t, setProcessMemoryLimit(1<<30)) + require.Equal(t, int64(1<<30), debug.SetMemoryLimit(-1)) + require.ErrorContains(t, setProcessMemoryLimit(-1), "non-negative") + require.Equal(t, int64(1<<30), debug.SetMemoryLimit(-1)) +}