lib/flagutil: show how dynamic flag defaults are calculated in -help output

This commit adds new methods into  `lib/flagutil` package. Which allows to add a hint for dynamic flags. It adds a new -help formatting formula - ``(default <number> = <text>)`.

For example:

```
-maxConcurrentInserts int
     The maximum number of concurrent insert requests. ... (default 16 = 2*cgroup.AvailableCPUs())
```

 This commit also updates `docs/Makefile` with a simpler sed regex.

Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9680
This commit is contained in:
Vandit Singh
2026-08-21 01:08:16 +05:30
committed by GitHub
parent 400f01a114
commit 364b3b6823
11 changed files with 134 additions and 40 deletions

View File

@@ -35,9 +35,10 @@ var (
"This can be changed with -promscrape.config.strictParse=false command-line flag")
maxIngestionRate = flag.Int("maxIngestionRate", 0, "The maximum number of samples vmsingle can receive per second. Data ingestion is paused when the limit is exceeded. "+
"By default there are no limits on samples ingestion rate.")
vmselectMaxConcurrentRequests = flag.Int("search.maxConcurrentRequests", getDefaultMaxConcurrentRequests(), "The maximum number of concurrent search requests. "+
"It shouldn't be high, since a single request can saturate all the CPU cores, while many concurrently executed requests may require high amounts of memory. "+
"See also -search.maxQueueDuration and -search.maxMemoryPerQuery")
vmselectMaxConcurrentRequests = flagutil.NewIntWithDynamicDefault("search.maxConcurrentRequests", getDefaultMaxConcurrentRequests(), "vmselect.getDefaultMaxConcurrentRequests()",
"The maximum number of concurrent search requests. "+
"It shouldn't be high, since a single request can saturate all the CPU cores, while many concurrently executed requests may require high amounts of memory. "+
"See also -search.maxQueueDuration and -search.maxMemoryPerQuery")
vmselectMaxQueueDuration = flag.Duration("search.maxQueueDuration", 10*time.Second, "The maximum time the request waits for execution when -search.maxConcurrentRequests "+
"limit is reached; see also -search.maxQueryDuration")
)

View File

@@ -63,9 +63,10 @@ var (
"See also -remoteWrite.maxDiskUsagePerURL and -remoteWrite.disableOnDiskQueue")
keepDanglingQueues = flag.Bool("remoteWrite.keepDanglingQueues", false, "Keep persistent queues contents at -remoteWrite.tmpDataPath in case there are no matching -remoteWrite.url. "+
"Useful when -remoteWrite.url is changed temporarily and persistent queue files will be needed later on.")
queues = flagutil.NewArrayInt("remoteWrite.queues", cgroup.AvailableCPUs()*2, "The number of concurrent queues to each -remoteWrite.url. Set more queues if default number of queues "+
"isn't enough for sending high volume of collected data to remote storage. "+
"Default value depends on the number of available CPU cores. It should work fine in most cases since it minimizes resource usage")
queues = flagutil.NewArrayIntWithDynamicDefault("remoteWrite.queues", cgroup.AvailableCPUs()*2, "2*cgroup.AvailableCPUs()",
"The number of concurrent queues to each -remoteWrite.url. Set more queues if default number of queues "+
"isn't enough for sending high volume of collected data to remote storage. "+
"Default value depends on the number of available CPU cores. It should work fine in most cases since it minimizes resource usage")
inmemoryQueues = flagutil.NewArrayInt("remoteWrite.inmemoryQueues", 0, "The number of additional workers per each -remoteWrite.url, which send only recently ingested data from the in-memory queue, "+
"while the file-based queue at -remoteWrite.tmpDataPath is drained by workers configured via -remoteWrite.queues. "+
"This reduces delivery lag for fresh samples when the file-based queue contains a backlog accumulated during remote storage outages.")

View File

@@ -36,9 +36,10 @@ var (
idleConnectionTimeout = flag.Duration("remoteWrite.idleConnTimeout", 50*time.Second, `Defines a duration for idle (keep-alive connections) to exist. Consider settings this value less to the value of "-http.idleConnTimeout". It must prevent possible "write: broken pipe" and "read: connection reset by peer" errors.`)
maxIdleConnections = flag.Int("remoteWrite.maxIdleConnections", 100, `Defines the number of idle (keep-alive connections) to -remoteWrite.url for the vmalert-tool debug writer, which sends every series in a separate request. Too low a value may result in a high number of sockets in TIME_WAIT state.`)
maxQueueSize = flag.Int("remoteWrite.maxQueueSize", defaultMaxQueueSize, "Defines the max number of pending datapoints to remote write endpoint")
maxBatchSize = flag.Int("remoteWrite.maxBatchSize", defaultMaxBatchSize, "Defines max number of timeseries to be flushed at once")
concurrency = flag.Int("remoteWrite.concurrency", defaultConcurrency, "Defines number of writers for concurrent writing into remote write endpoint. Default value depends on the number of available CPU cores.")
maxQueueSize = flag.Int("remoteWrite.maxQueueSize", defaultMaxQueueSize, "Defines the max number of pending datapoints to remote write endpoint")
maxBatchSize = flag.Int("remoteWrite.maxBatchSize", defaultMaxBatchSize, "Defines max number of timeseries to be flushed at once")
concurrency = flagutil.NewIntWithDynamicDefault("remoteWrite.concurrency", defaultConcurrency, "2*cgroup.AvailableCPUs()",
"Defines number of writers for concurrent writing into remote write endpoint. Default value depends on the number of available CPU cores.")
flushInterval = flag.Duration("remoteWrite.flushInterval", defaultFlushInterval, "Defines interval of flushes to remote write endpoint")
tlsInsecureSkipVerify = flag.Bool("remoteWrite.tlsInsecureSkipVerify", false, "Whether to skip tls verification when connecting to -remoteWrite.url")

View File

@@ -20,6 +20,7 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/cgroup"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/flagutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/querytracer"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage/metricnamestats"
@@ -30,11 +31,12 @@ var (
maxSamplesPerSeries = flag.Int("search.maxSamplesPerSeries", 30e6, "The maximum number of raw samples a single query can scan per each time series. This option allows limiting memory usage")
maxSamplesPerQuery = flag.Int("search.maxSamplesPerQuery", 1e9, "The maximum number of raw samples a single query can process across all time series. "+
"This protects from heavy queries, which select unexpectedly high number of raw samples. See also -search.maxSamplesPerSeries")
maxWorkersPerQuery = flag.Int("search.maxWorkersPerQuery", defaultMaxWorkersPerQuery, "The maximum number of CPU cores a single query can use. "+
"The default value should work good for most cases. "+
"The flag can be set to lower values for improving performance of big number of concurrently executed queries. "+
"The flag can be set to bigger values for improving performance of heavy queries, which scan big number of time series (>10K) and/or big number of samples (>100M). "+
"There is no sense in setting this flag to values bigger than the number of CPU cores available on the system")
maxWorkersPerQuery = flagutil.NewIntWithDynamicDefault("search.maxWorkersPerQuery", defaultMaxWorkersPerQuery, "netstorage.defaultMaxWorkersPerQuery()",
"The maximum number of CPU cores a single query can use. "+
"The default value should work good for most cases. "+
"The flag can be set to lower values for improving performance of big number of concurrently executed queries. "+
"The flag can be set to bigger values for improving performance of heavy queries, which scan big number of time series (>10K) and/or big number of samples (>100M). "+
"There is no sense in setting this flag to values bigger than the number of CPU cores available on the system")
)
// Result is a single timeseries result.

View File

@@ -90,12 +90,9 @@ endif
sed -i 's/\t/ /g' docs/victoriametrics/victoria_metrics_common_flags.md
sed -i 's/\t/ /g' docs/victoriametrics/victoria_metrics_enterprise_flags.md
# adjust flags with dynamic default values
# remove after https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9680 implemented
sed -i '/The maximum number of concurrent insert requests/ s/(default [0-9]\+)/(default 2*cgroup.AvailableCPUs())/' docs/victoriametrics/victoria_metrics_common_flags.md
sed -i '/The maximum number of concurrent search requests\./ s/(default [0-9]\+)/(default vmselect.getDefaultMaxConcurrentRequests())/' docs/victoriametrics/victoria_metrics_common_flags.md
sed -i '/The maximum number of CPU cores a single query can use\./ s/(default [0-9]\+)/(default netstorage.defaultMaxWorkersPerQuery())/' docs/victoriametrics/victoria_metrics_common_flags.md
sed -i '/The maximum number of concurrent goroutines to work with files;/ s/(default [0-9]\+)/(default fsutil.getDefaultConcurrency())/' docs/victoriametrics/victoria_metrics_common_flags.md
# hide the machine-specific value of dynamic defaults, keeping the formula.
# the flagutil.New*WithDynamicDefault constructors print them as "(default <value> = <formula>)".
sed -i 's/(default [0-9]\+ = \(.*\))$/(default \1)/' docs/victoriametrics/victoria_metrics_common_flags.md
docs-update-vmauth-flags:
ifndef TAG
@@ -119,9 +116,9 @@ endif
sed -i 's/\t/ /g' docs/victoriametrics/vmauth_common_flags.md
sed -i 's/\t/ /g' docs/victoriametrics/vmauth_enterprise_flags.md
# adjust flags with dynamic default values
# remove after https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9680 implemented
sed -i '/The maximum number of concurrent goroutines to work with files;/ s/(default [0-9]\+)/(default fsutil.getDefaultConcurrency())/' docs/victoriametrics/vmauth_common_flags.md
# hide the machine-specific value of dynamic defaults, keeping the formula.
# the flagutil.New*WithDynamicDefault constructors print them as "(default <value> = <formula>)".
sed -i 's/(default [0-9]\+ = \(.*\))$/(default \1)/' docs/victoriametrics/vmauth_common_flags.md
docs-update-vmagent-flags:
ifndef TAG
@@ -145,11 +142,9 @@ endif
sed -i 's/\t/ /g' docs/victoriametrics/vmagent_common_flags.md
sed -i 's/\t/ /g' docs/victoriametrics/vmagent_enterprise_flags.md
# adjust flags with dynamic default values
# remove after https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9680 implemented
sed -i '/The maximum number of concurrent insert requests/ s/(default [0-9]\+)/(default 2*cgroup.AvailableCPUs())/' docs/victoriametrics/vmagent_common_flags.md
sed -i '/The number of concurrent queues to each -remoteWrite.url./ s/(default [0-9]\+)/(default 2*cgroup.AvailableCPUs())/' docs/victoriametrics/vmagent_common_flags.md
sed -i '/The maximum number of concurrent goroutines to work with files;/ s/(default [0-9]\+)/(default fsutil.getDefaultConcurrency())/' docs/victoriametrics/vmagent_common_flags.md
# hide the machine-specific value of dynamic defaults, keeping the formula.
# the flagutil.New*WithDynamicDefault constructors print them as "(default <value> = <formula>)".
sed -i 's/(default [0-9]\+ = \(.*\))$/(default \1)/' docs/victoriametrics/vmagent_common_flags.md
docs-update-vmalert-flags:
ifndef TAG
@@ -173,10 +168,9 @@ endif
sed -i 's/\t/ /g' docs/victoriametrics/vmalert_common_flags.md
sed -i 's/\t/ /g' docs/victoriametrics/vmalert_enterprise_flags.md
# adjust flags with dynamic default values
# remove after https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9680 implemented
sed -i '/Defines number of writers for concurrent writing into remote write endpoint./ s/(default [0-9]\+)/(default 2*cgroup.AvailableCPUs())/' docs/victoriametrics/vmalert_common_flags.md
sed -i '/The maximum number of concurrent goroutines to work with files;/ s/(default [0-9]\+)/(default fsutil.getDefaultConcurrency())/' docs/victoriametrics/vmalert_common_flags.md
# hide the machine-specific value of dynamic defaults, keeping the formula.
# the flagutil.New*WithDynamicDefault constructors print them as "(default <value> = <formula>)".
sed -i 's/(default [0-9]\+ = \(.*\))$/(default \1)/' docs/victoriametrics/vmalert_common_flags.md
docs-update-vmselect-flags:
ifndef TAG

View File

@@ -28,6 +28,7 @@ See also [LTS releases](https://docs.victoriametrics.com/victoriametrics/lts-rel
* FEATURE: [vmagent](https://docs.victoriametrics.com/victoriametrics/vmagent/), [vmsingle](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/), `vmstorage` and `vmselect` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/): expose the `vm_app_prev_shutdown_unclean` gauge. It is set to `1` when the previous process run didn't shut down cleanly. Added the `UncleanShutdown` [alerting rule](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/deployment/docker/rules/alerts-health.yml), which fires for 10 minutes after an unclean shutdown is detected. See [#8443](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8443).
* FEATURE: [vmui](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#vmui): show the selected time zone UTC offset next to the date/time controls and allow opening time zone settings from it. See [#11332](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11332).
* FEATURE: [vmsingle](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/), [vmagent](https://docs.victoriametrics.com/victoriametrics/vmagent/), [vmalert](https://docs.victoriametrics.com/victoriametrics/vmalert/), and `vmselect` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/): show how the default value is calculated for command-line flags which derive it from the number of available CPU cores. For example, `-maxConcurrentInserts` now prints `(default 16 = 2*cgroup.AvailableCPUs())` in `-help` output instead of `(default 16)`. Updated flags: `-search.maxConcurrentRequests`, `-search.maxWorkersPerQuery`, `-fs.maxConcurrency`, `-remoteWrite.concurrency`, `-remoteWrite.queues`. See [#9680](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9680). Thanks to @Vandit1604 for contribution.
* BUGFIX: [vmagent](https://docs.victoriametrics.com/victoriametrics/vmagent/) and `vminsert` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/): fix infinite loop in the OpenTelemetry Firehose ingestion endpoint (`/opentelemetry/api/v1/push`) when receiving a malformed record with an incomplete varint in the `data` field. Previously this caused the goroutine to spin forever, permanently consuming CPU until the process was restarted.
* BUGFIX: [vmalert-tool](https://docs.victoriametrics.com/victoriametrics/vmalert-tool/): reuse connections to `-remoteWrite.url` when writing the results of recording rules and alerts. Previously every series was sent over a new connection, which left a lot of sockets in `TIME_WAIT` state and could exhaust the ephemeral port range. The number of idle connections can be tuned via the new `-remoteWrite.maxIdleConnections` command-line flag. Thanks @evkuzin for contribution.

View File

@@ -39,8 +39,29 @@ func NewArrayBool(name, description string) *ArrayBool {
}
// NewArrayInt returns new ArrayInt with the given name, defaultValue and description.
//
// -help shows defaultValue as a plain number. Use NewArrayIntWithDynamicDefault when
// defaultValue is calculated at runtime.
func NewArrayInt(name string, defaultValue int, description string) *ArrayInt {
description += fmt.Sprintf(" (default %d)", defaultValue)
return newArrayInt(name, defaultValue, strconv.Itoa(defaultValue), description)
}
// NewArrayIntWithDynamicDefault returns new ArrayInt with the given name, defaultValue and description.
//
// Use it instead of NewArrayInt when defaultValue is calculated at runtime.
// See NewIntWithDynamicDefault for why such a value needs a hint.
func NewArrayIntWithDynamicDefault(name string, defaultValue int, defaultValueHint, description string) *ArrayInt {
if defaultValueHint == "" {
panic(fmt.Sprintf("BUG: missing defaultValueHint for -%s", name))
}
return newArrayInt(name, defaultValue, fmt.Sprintf("%d = %s", defaultValue, defaultValueHint), description)
}
// newArrayInt registers an int array flag, which shows defaultValueText as its default in -help.
//
// Array flags keep the default in the description, since flag.Var hides an empty DefValue.
func newArrayInt(name string, defaultValue int, defaultValueText, description string) *ArrayInt {
description += fmt.Sprintf(" (default %s)", defaultValueText)
description += "\nSupports `array` of values separated by comma or specified via multiple flags."
description += "\nEmpty values are set to default value."
a := &ArrayInt{

View File

@@ -7,6 +7,25 @@ import (
"strings"
)
// NewIntWithDynamicDefault returns a new int flag with the given name, defaultValue and description.
//
// Use it instead of flag.Int when defaultValue is calculated at runtime, for example
// from the number of CPU cores. Such a value differs per machine, so -help shows both
// the value and defaultValueHint, for example "16 = 2 * availableCPUs".
//
// Only -help output changes. The flag value stays defaultValue.
func NewIntWithDynamicDefault(name string, defaultValue int, defaultValueHint, description string) *int {
if defaultValueHint == "" {
panic(fmt.Sprintf("BUG: missing defaultValueHint for -%s", name))
}
p := flag.Int(name, defaultValue, description)
// DefValue is only the text shown by -help: "default value (as text); for usage message".
flag.Lookup(name).DefValue = fmt.Sprintf("%d = %s", defaultValue, defaultValueHint)
return p
}
// WriteFlags writes all the explicitly set flags to w.
func WriteFlags(w io.Writer) {
flag.Visit(func(f *flag.Flag) {

51
lib/flagutil/flag_test.go Normal file
View File

@@ -0,0 +1,51 @@
package flagutil
import (
"flag"
"strings"
"testing"
)
// The flags are registered at package level, since flag registration panics when it repeats.
var (
fooFlagIntDynamicDefault = NewIntWithDynamicDefault("fooFlagIntDynamicDefault", 42, "2 * availableCPUs", "test")
fooFlagArrayIntDynamicDefault = NewArrayIntWithDynamicDefault("fooFlagArrayIntDynamicDefault", 42, "2 * availableCPUs", "test")
fooFlagArrayIntPlainDefault = NewArrayInt("fooFlagArrayIntPlainDefault", 42, "test")
)
func TestNewIntWithDynamicDefaultSuccess(t *testing.T) {
// -help must show the value together with the hint.
f := flag.Lookup("fooFlagIntDynamicDefault")
if f.DefValue != "42 = 2 * availableCPUs" {
t.Fatalf("unexpected DefValue; got %q; want %q", f.DefValue, "42 = 2 * availableCPUs")
}
// the flag value must stay the calculated one.
if *fooFlagIntDynamicDefault != 42 {
t.Fatalf("unexpected flag value; got %d; want %d", *fooFlagIntDynamicDefault, 42)
}
}
func TestNewArrayIntWithDynamicDefaultSuccess(t *testing.T) {
// array flags keep the default in the description, so the hint must go there.
f := flag.Lookup("fooFlagArrayIntDynamicDefault")
if !strings.Contains(f.Usage, "(default 42 = 2 * availableCPUs)") {
t.Fatalf("missing the hint in the flag description; got %q", f.Usage)
}
// the default value must stay the calculated one.
if n := fooFlagArrayIntDynamicDefault.GetOptionalArg(0); n != 42 {
t.Fatalf("unexpected default value; got %d; want %d", n, 42)
}
}
func TestNewArrayIntKeepsPlainDefault(t *testing.T) {
// NewArrayInt must keep showing a plain number, since it shares the body with the dynamic one.
f := flag.Lookup("fooFlagArrayIntPlainDefault")
if !strings.Contains(f.Usage, "(default 42)") {
t.Fatalf("unexpected flag description; got %q", f.Usage)
}
if n := fooFlagArrayIntPlainDefault.GetOptionalArg(0); n != 42 {
t.Fatalf("unexpected default value; got %d; want %d", n, 42)
}
}

View File

@@ -1,14 +1,15 @@
package fsutil
import (
"flag"
"sync"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/cgroup"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/flagutil"
)
var maxConcurrency = flag.Int("fs.maxConcurrency", getDefaultConcurrency(), "The maximum number of concurrent goroutines to work with files; smaller values may help reducing Go scheduling latency "+
"on systems with small number of CPU cores; higher values may help reducing data ingestion latency on systems with high-latency storage such as NFS or Ceph")
var maxConcurrency = flagutil.NewIntWithDynamicDefault("fs.maxConcurrency", getDefaultConcurrency(), "fsutil.getDefaultConcurrency()",
"The maximum number of concurrent goroutines to work with files; smaller values may help reducing Go scheduling latency "+
"on systems with small number of CPU cores; higher values may help reducing data ingestion latency on systems with high-latency storage such as NFS or Ceph")
func getDefaultConcurrency() int {
n := min(16*cgroup.AvailableCPUs(), 256)

View File

@@ -10,16 +10,18 @@ import (
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/cgroup"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/flagutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/timerpool"
"github.com/VictoriaMetrics/metrics"
)
var (
maxConcurrentInserts = flag.Int("maxConcurrentInserts", 2*cgroup.AvailableCPUs(), "The maximum number of concurrent insert requests. "+
"Set higher value when clients send data over slow networks. "+
"Default value depends on the number of available CPU cores. It should work fine in most cases since it minimizes resource usage. "+
"See also -insert.maxQueueDuration")
maxConcurrentInserts = flagutil.NewIntWithDynamicDefault("maxConcurrentInserts", 2*cgroup.AvailableCPUs(), "2*cgroup.AvailableCPUs()",
"The maximum number of concurrent insert requests. "+
"Set higher value when clients send data over slow networks. "+
"Default value depends on the number of available CPU cores. It should work fine in most cases since it minimizes resource usage. "+
"See also -insert.maxQueueDuration")
maxQueueDuration = flag.Duration("insert.maxQueueDuration", time.Minute, "The maximum duration to wait in the queue when -maxConcurrentInserts "+
"concurrent insert requests are executed")
)