From 02b2548c632927c955c35941c90705f646b82bfd Mon Sep 17 00:00:00 2001 From: Nicolai Antiferov Date: Thu, 28 Aug 2025 22:48:55 +0200 Subject: [PATCH] Feature: add include-go-runtime-metrics flag to enable Go runtime metrics * Add include-go-runtime-metrics flag to enable Go runtime metrics, default disabled * Add test suite for inclGoRuntimeMetrics to main_test.go * Fix description for redisMetricsOnly flag Fix #1034 --- README.md | 10 +++++----- main.go | 15 ++++++++++----- main_test.go | 30 +++++++++++++++++++----------- 3 files changed, 34 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index acc03190..b1548354 100644 --- a/README.md +++ b/README.md @@ -186,7 +186,8 @@ scrape_configs: | connection-timeout | REDIS_EXPORTER_CONNECTION_TIMEOUT | Timeout for connection to Redis instance, defaults to "15s" (in Golang duration format) | | web.listen-address | REDIS_EXPORTER_WEB_LISTEN_ADDRESS | Address to listen on for web interface and telemetry, defaults to `0.0.0.0:9121`. | | web.telemetry-path | REDIS_EXPORTER_WEB_TELEMETRY_PATH | Path under which to expose metrics, defaults to `/metrics`. | -| redis-only-metrics | REDIS_EXPORTER_REDIS_ONLY_METRICS | Whether to also export go runtime metrics, defaults to false. | +| redis-only-metrics | REDIS_EXPORTER_REDIS_ONLY_METRICS | Whether to export only Redis metrics (omit Go process+runtime metrics), defaults to false. | +| include-go-runtime-metrics | REDIS_EXPORTER_INCLUDE_GO_RUNTIME_METRICS | Whether to include Go runtime metrics, defaults to false. | | include-config-metrics | REDIS_EXPORTER_INCL_CONFIG_METRICS | Whether to include all config settings as metrics, defaults to false. | | include-system-metrics | REDIS_EXPORTER_INCL_SYSTEM_METRICS | Whether to include system metrics like `total_system_memory_bytes`, defaults to false. | | include-modules-metrics | REDIS_EXPORTER_INCL_MODULES_METRICS | Whether to collect Redis Modules metrics, defaults to false. | @@ -209,9 +210,9 @@ scrape_configs: | check-key-groups | REDIS_EXPORTER_CHECK_KEY_GROUPS | Comma separated list of [LUA regexes](https://www.lua.org/pil/20.1.html) for classifying keys into groups. The regexes are applied in specified order to individual keys, and the group name is generated by concatenating all capture groups of the first regex that matches a key. A key will be tracked under the `unclassified` group if none of the specified regexes matches it. | | max-distinct-key-groups | REDIS_EXPORTER_MAX_DISTINCT_KEY_GROUPS | Maximum number of distinct key groups that can be tracked independently *per Redis database*. If exceeded, only key groups with the highest memory consumption within the limit will be tracked separately, all remaining key groups will be tracked under a single `overflow` key group. | | config-command | REDIS_EXPORTER_CONFIG_COMMAND | What to use for the CONFIG command, defaults to `CONFIG`, , set to "-" to skip config metrics extraction. | -| basic-auth-username | REDIS_EXPORTER_BASIC_AUTH_USERNAME | Username for Basic Authentication with the redis exporter needs to be set together with basic-auth-password to be effective -| basic-auth-password | REDIS_EXPORTER_BASIC_AUTH_PASSWORD | Password for Basic Authentication with the redis exporter needs to be set together with basic-auth-username to be effective -| include-metrics-for-empty-databases | REDIS_EXPORTER_INCL_METRICS_FOR_EMPTY_DATABASES | Whether to emit db metrics (like db_keys) for empty databases +| basic-auth-username | REDIS_EXPORTER_BASIC_AUTH_USERNAME | Username for Basic Authentication with the redis exporter needs to be set together with basic-auth-password to be effective +| basic-auth-password | REDIS_EXPORTER_BASIC_AUTH_PASSWORD | Password for Basic Authentication with the redis exporter needs to be set together with basic-auth-username to be effective +| include-metrics-for-empty-databases | REDIS_EXPORTER_INCL_METRICS_FOR_EMPTY_DATABASES | Whether to emit db metrics (like db_keys) for empty databases Redis instance addresses can be tcp addresses: `redis://localhost:6379`, `redis.example.com:6379` or e.g. unix sockets: `unix:///tmp/redis.sock`.\ SSL is supported by using the `rediss://` schema, for example: `rediss://azure-ssl-enabled-host.redis.cache.windows.net:6380` (note that the port is required when connecting to a non-standard 6379 port, e.g. with Azure Redis instances).\ @@ -378,4 +379,3 @@ Or you can bring up the stack, run the tests, and then tear down the stack, all ## Communal effort Open an issue or PR if you have more suggestions, questions or ideas about what to add. - diff --git a/main.go b/main.go index 8ee17cdb..8c129d3e 100644 --- a/main.go +++ b/main.go @@ -124,15 +124,19 @@ func setupLogging(isDebug bool, logLevel, logFormat string) error { } // createPrometheusRegistry creates and configures a Prometheus registry -func createPrometheusRegistry(redisMetricsOnly bool) *prometheus.Registry { +func createPrometheusRegistry(redisMetricsOnly, inclGoRuntimeMetrics bool) *prometheus.Registry { registry := prometheus.NewRegistry() if !redisMetricsOnly { registry.MustRegister( // expose process metrics like CPU, Memory, file descriptor usage etc. collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}), - // expose all Go runtime metrics like GC stats, memory stats etc. - collectors.NewGoCollector(collectors.WithGoCollectorRuntimeMetrics(collectors.MetricsAll)), ) + if inclGoRuntimeMetrics { + registry.MustRegister( + // expose all Go runtime metrics like GC stats, memory stats etc. + collectors.NewGoCollector(collectors.WithGoCollectorRuntimeMetrics(collectors.MetricsAll)), + ) + } } return registry } @@ -174,7 +178,8 @@ func main() { exportClientList = flag.Bool("export-client-list", getEnvBool("REDIS_EXPORTER_EXPORT_CLIENT_LIST", false), "Whether to scrape Client List specific metrics") exportClientPort = flag.Bool("export-client-port", getEnvBool("REDIS_EXPORTER_EXPORT_CLIENT_PORT", false), "Whether to include the client's port when exporting the client list. Warning: including the port increases the number of metrics generated and will make your Prometheus server take up more memory") showVersion = flag.Bool("version", false, "Show version information and exit") - redisMetricsOnly = flag.Bool("redis-only-metrics", getEnvBool("REDIS_EXPORTER_REDIS_ONLY_METRICS", false), "Whether to also export go runtime metrics") + redisMetricsOnly = flag.Bool("redis-only-metrics", getEnvBool("REDIS_EXPORTER_REDIS_ONLY_METRICS", false), "Whether to export only Redis metrics (omit Go process+runtime metrics)") + inclGoRuntimeMetrics = flag.Bool("include-go-runtime-metrics", getEnvBool("REDIS_EXPORTER_INCLUDE_GO_RUNTIME_METRICS", false), "Whether to include Go runtime metrics") pingOnConnect = flag.Bool("ping-on-connect", getEnvBool("REDIS_EXPORTER_PING_ON_CONNECT", false), "Whether to ping the redis instance after connecting") inclConfigMetrics = flag.Bool("include-config-metrics", getEnvBool("REDIS_EXPORTER_INCL_CONFIG_METRICS", false), "Whether to include all config settings as metrics") inclModulesMetrics = flag.Bool("include-modules-metrics", getEnvBool("REDIS_EXPORTER_INCL_MODULES_METRICS", false), "Whether to collect Redis Modules metrics") @@ -229,7 +234,7 @@ func main() { log.Fatalf("Error loading script files: %s", err) } - registry := createPrometheusRegistry(*redisMetricsOnly) + registry := createPrometheusRegistry(*redisMetricsOnly, *inclGoRuntimeMetrics) exp, err := exporter.NewRedisExporter( *redisAddr, diff --git a/main_test.go b/main_test.go index 3343fbf4..c2d014d0 100644 --- a/main_test.go +++ b/main_test.go @@ -402,19 +402,21 @@ func TestSetupLogging(t *testing.T) { func TestCreatePrometheusRegistry(t *testing.T) { tests := []struct { - name string - redisMetricsOnly bool - description string + name string + redisMetricsOnly bool + inclGoRuntimeMetrics bool + description string }{ - {"redis metrics only", true, "should create registry with only Redis metrics"}, - {"all metrics", false, "should create registry with process and Go metrics"}, + {"redis metrics only", true, false, "should create registry with only Redis metrics"}, + {"redis + process metrics", false, false, "should create registry with Redis and process metrics"}, + {"all metrics", false, true, "should create registry with process and Go metrics"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - registry := createPrometheusRegistry(tt.redisMetricsOnly) + registry := createPrometheusRegistry(tt.redisMetricsOnly, tt.inclGoRuntimeMetrics) if registry == nil { - t.Errorf("createPrometheusRegistry(%v) returned nil registry", tt.redisMetricsOnly) + t.Errorf("createPrometheusRegistry(%v, %v) returned nil registry", tt.redisMetricsOnly, tt.inclGoRuntimeMetrics) } // Verify it's a valid Prometheus registry (it's already *prometheus.Registry) @@ -422,14 +424,20 @@ func TestCreatePrometheusRegistry(t *testing.T) { // Basic functionality test - gather metrics metricFamilies, err := registry.Gather() if err != nil { - t.Errorf("createPrometheusRegistry(%v) registry.Gather() error: %v", tt.redisMetricsOnly, err) + t.Errorf("createPrometheusRegistry(%v, %v) registry.Gather() error: %v", tt.redisMetricsOnly, tt.inclGoRuntimeMetrics, err) } - // When redisMetricsOnly=false, we should have process/Go metrics + // When redisMetricsOnly=false, inclGoRuntimeMetrics=false we should have process metrics + // When redisMetricsOnly=false, inclGoRuntimeMetrics=true we should have Go process+runtime metrics // When redisMetricsOnly=true, we should have fewer (or no) built-in metrics if !tt.redisMetricsOnly { if len(metricFamilies) == 0 { - t.Errorf("createPrometheusRegistry(false) expected process/Go metrics but got none") + t.Errorf("createPrometheusRegistry(%v, %v) expected Go process+runtime metrics but got none", tt.redisMetricsOnly, tt.inclGoRuntimeMetrics) + } + if !tt.inclGoRuntimeMetrics { + if len(metricFamilies) > 20 { // process metrics count is less than 10 + t.Errorf("createPrometheusRegistry(%v, %v) expected only process metrics but got %v metrics", tt.redisMetricsOnly, tt.inclGoRuntimeMetrics, len(metricFamilies)) + } } } }) @@ -484,7 +492,7 @@ func TestMainFunctionsIntegration(t *testing.T) { } // Test registry creation - registry := createPrometheusRegistry(true) + registry := createPrometheusRegistry(true, false) if registry == nil { t.Error("Registry creation failed") }