• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

oliver006 / redis_exporter / 17172001870

23 Aug 2025 05:58AM UTC coverage: 84.921% (-0.9%) from 85.87%
17172001870

Pull #1028

github

web-flow
Merge 891f7f01e into 7632b7b20
Pull Request #1028: sirupsen/log --> log/slog

121 of 249 new or added lines in 18 files covered. (48.59%)

6 existing lines in 1 file now uncovered.

2568 of 3024 relevant lines covered (84.92%)

13254.26 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.57
/exporter/exporter.go
1
package exporter
2

3
import (
4
        "fmt"
5
        "log/slog"
6
        "net/http"
7
        "net/url"
8
        "runtime"
9
        "strconv"
10
        "strings"
11
        "sync"
12
        "time"
13

14
        // see https://github.com/prometheus/client_golang/releases/tag/v1.22.0
15
        _ "github.com/prometheus/client_golang/prometheus/promhttp/zstd"
16

17
        "github.com/gomodule/redigo/redis"
18
        "github.com/prometheus/client_golang/prometheus"
19
        "github.com/prometheus/client_golang/prometheus/promhttp"
20
)
21

22
type BuildInfo struct {
23
        Version   string
24
        CommitSha string
25
        Date      string
26
}
27

28
// Exporter implements the prometheus.Exporter interface, and exports Redis metrics.
29
type Exporter struct {
30
        sync.Mutex
31

32
        redisAddr string
33

34
        totalScrapes              prometheus.Counter
35
        scrapeDuration            prometheus.Summary
36
        targetScrapeRequestErrors prometheus.Counter
37

38
        metricDescriptions map[string]*prometheus.Desc
39

40
        options Options
41

42
        metricMapCounters map[string]string
43
        metricMapGauges   map[string]string
44

45
        mux *http.ServeMux
46

47
        buildInfo BuildInfo
48
}
49

50
type Options struct {
51
        User                           string
52
        Password                       string
53
        Namespace                      string
54
        PasswordMap                    map[string]string
55
        ConfigCommandName              string
56
        CheckKeys                      string
57
        CheckSingleKeys                string
58
        CheckStreams                   string
59
        CheckSingleStreams             string
60
        StreamsExcludeConsumerMetrics  bool
61
        CheckKeysBatchSize             int64
62
        CheckKeyGroups                 string
63
        MaxDistinctKeyGroups           int64
64
        CountKeys                      string
65
        LuaScript                      map[string][]byte
66
        ClientCertFile                 string
67
        ClientKeyFile                  string
68
        CaCertFile                     string
69
        InclConfigMetrics              bool
70
        InclModulesMetrics             bool
71
        DisableExportingKeyValues      bool
72
        ExcludeLatencyHistogramMetrics bool
73
        RedactConfigMetrics            bool
74
        InclSystemMetrics              bool
75
        SkipTLSVerification            bool
76
        SetClientName                  bool
77
        IsTile38                       bool
78
        IsCluster                      bool
79
        ExportClientList               bool
80
        ExportClientsInclPort          bool
81
        ConnectionTimeouts             time.Duration
82
        MetricsPath                    string
83
        RedisMetricsOnly               bool
84
        PingOnConnect                  bool
85
        RedisPwdFile                   string
86
        Registry                       *prometheus.Registry
87
        BuildInfo                      BuildInfo
88
        BasicAuthUsername              string
89
        BasicAuthPassword              string
90
        SkipCheckKeysForRoleMaster     bool
91
        InclMetricsForEmptyDatabases   bool
92
}
93

94
// NewRedisExporter returns a new exporter of Redis metrics.
95
func NewRedisExporter(uri string, opts Options) (*Exporter, error) {
2,171✔
96
        slog.Debug("NewRedisExporter options", "options", opts)
2,171✔
97

2,171✔
98
        switch {
2,171✔
99
        case strings.HasPrefix(uri, "valkey://"):
310✔
100
                uri = strings.Replace(uri, "valkey://", "redis://", 1)
310✔
101
        case strings.HasPrefix(uri, "valkeys://"):
1✔
102
                uri = strings.Replace(uri, "valkeys://", "rediss://", 1)
1✔
103
        }
104

105
        slog.Debug("Using redis URI", "uri", uri)
2,171✔
106

2,171✔
107
        if opts.Registry == nil {
2,331✔
108
                opts.Registry = prometheus.NewRegistry()
160✔
109
        }
160✔
110

111
        e := &Exporter{
2,171✔
112
                redisAddr: uri,
2,171✔
113
                options:   opts,
2,171✔
114

2,171✔
115
                buildInfo: opts.BuildInfo,
2,171✔
116

2,171✔
117
                totalScrapes: prometheus.NewCounter(prometheus.CounterOpts{
2,171✔
118
                        Namespace: opts.Namespace,
2,171✔
119
                        Name:      "exporter_scrapes_total",
2,171✔
120
                        Help:      "Current total redis scrapes.",
2,171✔
121
                }),
2,171✔
122

2,171✔
123
                scrapeDuration: prometheus.NewSummary(prometheus.SummaryOpts{
2,171✔
124
                        Namespace: opts.Namespace,
2,171✔
125
                        Name:      "exporter_scrape_duration_seconds",
2,171✔
126
                        Help:      "Durations of scrapes by the exporter",
2,171✔
127
                }),
2,171✔
128

2,171✔
129
                targetScrapeRequestErrors: prometheus.NewCounter(prometheus.CounterOpts{
2,171✔
130
                        Namespace: opts.Namespace,
2,171✔
131
                        Name:      "target_scrape_request_errors_total",
2,171✔
132
                        Help:      "Errors in requests to the exporter",
2,171✔
133
                }),
2,171✔
134

2,171✔
135
                metricMapGauges: map[string]string{
2,171✔
136
                        // # Server
2,171✔
137
                        "uptime_in_seconds": "uptime_in_seconds",
2,171✔
138
                        "process_id":        "process_id",
2,171✔
139
                        "io_threads_active": "io_threads_active",
2,171✔
140

2,171✔
141
                        // # Clients
2,171✔
142
                        "connected_clients":            "connected_clients",
2,171✔
143
                        "blocked_clients":              "blocked_clients",
2,171✔
144
                        "maxclients":                   "max_clients",
2,171✔
145
                        "tracking_clients":             "tracking_clients",
2,171✔
146
                        "clients_in_timeout_table":     "clients_in_timeout_table",
2,171✔
147
                        "pubsub_clients":               "pubsub_clients",               // Added in Redis 7.4
2,171✔
148
                        "watching_clients":             "watching_clients",             // Added in Redis 7.4
2,171✔
149
                        "total_watched_keys":           "total_watched_keys",           // Added in Redis 7.4
2,171✔
150
                        "total_blocking_keys":          "total_blocking_keys",          // Added in Redis 7.2
2,171✔
151
                        "total_blocking_keys_on_nokey": "total_blocking_keys_on_nokey", // Added in Redis 7.2
2,171✔
152

2,171✔
153
                        // redis 2,3,4.x
2,171✔
154
                        "client_longest_output_list": "client_longest_output_list",
2,171✔
155
                        "client_biggest_input_buf":   "client_biggest_input_buf",
2,171✔
156

2,171✔
157
                        // the above two metrics were renamed in redis 5.x
2,171✔
158
                        "client_recent_max_output_buffer": "client_recent_max_output_buffer_bytes",
2,171✔
159
                        "client_recent_max_input_buffer":  "client_recent_max_input_buffer_bytes",
2,171✔
160

2,171✔
161
                        // # Memory
2,171✔
162
                        "allocator_active":     "allocator_active_bytes",
2,171✔
163
                        "allocator_allocated":  "allocator_allocated_bytes",
2,171✔
164
                        "allocator_resident":   "allocator_resident_bytes",
2,171✔
165
                        "allocator_frag_ratio": "allocator_frag_ratio",
2,171✔
166
                        "allocator_frag_bytes": "allocator_frag_bytes",
2,171✔
167
                        "allocator_muzzy":      "allocator_muzzy_bytes",
2,171✔
168
                        "allocator_rss_ratio":  "allocator_rss_ratio",
2,171✔
169
                        "allocator_rss_bytes":  "allocator_rss_bytes",
2,171✔
170

2,171✔
171
                        "used_memory":              "memory_used_bytes",
2,171✔
172
                        "used_memory_rss":          "memory_used_rss_bytes",
2,171✔
173
                        "used_memory_peak":         "memory_used_peak_bytes",
2,171✔
174
                        "used_memory_lua":          "memory_used_lua_bytes",
2,171✔
175
                        "used_memory_vm_eval":      "memory_used_vm_eval_bytes",      // Added in Redis 7.0
2,171✔
176
                        "used_memory_scripts_eval": "memory_used_scripts_eval_bytes", // Added in Redis 7.0
2,171✔
177
                        "used_memory_overhead":     "memory_used_overhead_bytes",
2,171✔
178
                        "used_memory_startup":      "memory_used_startup_bytes",
2,171✔
179
                        "used_memory_dataset":      "memory_used_dataset_bytes",
2,171✔
180
                        "number_of_cached_scripts": "number_of_cached_scripts",       // Added in Redis 7.0
2,171✔
181
                        "number_of_functions":      "number_of_functions",            // Added in Redis 7.0
2,171✔
182
                        "number_of_libraries":      "number_of_libraries",            // Added in Redis 7.4
2,171✔
183
                        "used_memory_vm_functions": "memory_used_vm_functions_bytes", // Added in Redis 7.0
2,171✔
184
                        "used_memory_scripts":      "memory_used_scripts_bytes",      // Added in Redis 7.0
2,171✔
185
                        "used_memory_functions":    "memory_used_functions_bytes",    // Added in Redis 7.0
2,171✔
186
                        "used_memory_vm_total":     "memory_used_vm_total",           // Added in Redis 7.0
2,171✔
187
                        "maxmemory":                "memory_max_bytes",
2,171✔
188

2,171✔
189
                        "maxmemory_reservation":         "memory_max_reservation_bytes",
2,171✔
190
                        "maxmemory_desired_reservation": "memory_max_reservation_desired_bytes",
2,171✔
191

2,171✔
192
                        "maxfragmentationmemory_reservation":         "memory_max_fragmentation_reservation_bytes",
2,171✔
193
                        "maxfragmentationmemory_desired_reservation": "memory_max_fragmentation_reservation_desired_bytes",
2,171✔
194

2,171✔
195
                        "mem_fragmentation_ratio": "mem_fragmentation_ratio",
2,171✔
196
                        "mem_fragmentation_bytes": "mem_fragmentation_bytes",
2,171✔
197
                        "mem_clients_slaves":      "mem_clients_slaves",
2,171✔
198
                        "mem_clients_normal":      "mem_clients_normal",
2,171✔
199
                        "mem_cluster_links":       "mem_cluster_links_bytes",
2,171✔
200
                        "mem_aof_buffer":          "mem_aof_buffer_bytes",
2,171✔
201
                        "mem_replication_backlog": "mem_replication_backlog_bytes",
2,171✔
202

2,171✔
203
                        "expired_stale_perc": "expired_stale_percentage",
2,171✔
204

2,171✔
205
                        // https://github.com/antirez/redis/blob/17bf0b25c1171486e3a1b089f3181fff2bc0d4f0/src/evict.c#L349-L352
2,171✔
206
                        // ... the sum of AOF and slaves buffer ...
2,171✔
207
                        "mem_not_counted_for_evict":           "mem_not_counted_for_eviction_bytes",
2,171✔
208
                        "mem_total_replication_buffers":       "mem_total_replication_buffers_bytes",       // Added in Redis 7.0
2,171✔
209
                        "mem_overhead_db_hashtable_rehashing": "mem_overhead_db_hashtable_rehashing_bytes", // Added in Redis 7.4
2,171✔
210

2,171✔
211
                        "lazyfree_pending_objects": "lazyfree_pending_objects",
2,171✔
212
                        "lazyfreed_objects":        "lazyfreed_objects",
2,171✔
213
                        "active_defrag_running":    "active_defrag_running",
2,171✔
214

2,171✔
215
                        "migrate_cached_sockets": "migrate_cached_sockets_total",
2,171✔
216

2,171✔
217
                        "active_defrag_hits":       "defrag_hits",
2,171✔
218
                        "active_defrag_misses":     "defrag_misses",
2,171✔
219
                        "active_defrag_key_hits":   "defrag_key_hits",
2,171✔
220
                        "active_defrag_key_misses": "defrag_key_misses",
2,171✔
221

2,171✔
222
                        // https://github.com/antirez/redis/blob/0af467d18f9d12b137af3b709c0af579c29d8414/src/expire.c#L297-L299
2,171✔
223
                        "expired_time_cap_reached_count": "expired_time_cap_reached_total",
2,171✔
224

2,171✔
225
                        // # Persistence
2,171✔
226
                        "loading":                      "loading_dump_file",
2,171✔
227
                        "async_loading":                "async_loading", // Added in Redis 7.0
2,171✔
228
                        "rdb_changes_since_last_save":  "rdb_changes_since_last_save",
2,171✔
229
                        "rdb_bgsave_in_progress":       "rdb_bgsave_in_progress",
2,171✔
230
                        "rdb_last_save_time":           "rdb_last_save_timestamp_seconds",
2,171✔
231
                        "rdb_last_bgsave_status":       "rdb_last_bgsave_status",
2,171✔
232
                        "rdb_last_bgsave_time_sec":     "rdb_last_bgsave_duration_sec",
2,171✔
233
                        "rdb_current_bgsave_time_sec":  "rdb_current_bgsave_duration_sec",
2,171✔
234
                        "rdb_saves":                    "rdb_saves_total",
2,171✔
235
                        "rdb_last_cow_size":            "rdb_last_cow_size_bytes",
2,171✔
236
                        "rdb_last_load_keys_expired":   "rdb_last_load_expired_keys", // Added in Redis 7.0
2,171✔
237
                        "rdb_last_load_keys_loaded":    "rdb_last_load_loaded_keys",  // Added in Redis 7.0
2,171✔
238
                        "aof_enabled":                  "aof_enabled",
2,171✔
239
                        "aof_rewrite_in_progress":      "aof_rewrite_in_progress",
2,171✔
240
                        "aof_rewrite_scheduled":        "aof_rewrite_scheduled",
2,171✔
241
                        "aof_last_rewrite_time_sec":    "aof_last_rewrite_duration_sec",
2,171✔
242
                        "aof_current_rewrite_time_sec": "aof_current_rewrite_duration_sec",
2,171✔
243
                        "aof_last_cow_size":            "aof_last_cow_size_bytes",
2,171✔
244
                        "aof_current_size":             "aof_current_size_bytes",
2,171✔
245
                        "aof_base_size":                "aof_base_size_bytes",
2,171✔
246
                        "aof_pending_rewrite":          "aof_pending_rewrite",
2,171✔
247
                        "aof_buffer_length":            "aof_buffer_length",
2,171✔
248
                        "aof_rewrite_buffer_length":    "aof_rewrite_buffer_length", // Added in Redis 7.0
2,171✔
249
                        "aof_pending_bio_fsync":        "aof_pending_bio_fsync",
2,171✔
250
                        "aof_delayed_fsync":            "aof_delayed_fsync",
2,171✔
251
                        "aof_last_bgrewrite_status":    "aof_last_bgrewrite_status",
2,171✔
252
                        "aof_last_write_status":        "aof_last_write_status",
2,171✔
253
                        "module_fork_in_progress":      "module_fork_in_progress",
2,171✔
254
                        "module_fork_last_cow_size":    "module_fork_last_cow_size",
2,171✔
255

2,171✔
256
                        // # Stats
2,171✔
257
                        "current_eviction_exceeded_time": "current_eviction_exceeded_time_ms",
2,171✔
258
                        "pubsub_channels":                "pubsub_channels",
2,171✔
259
                        "pubsub_patterns":                "pubsub_patterns",
2,171✔
260
                        "pubsubshard_channels":           "pubsubshard_channels", // Added in Redis 7.0.3
2,171✔
261
                        "latest_fork_usec":               "latest_fork_usec",
2,171✔
262
                        "tracking_total_keys":            "tracking_total_keys",
2,171✔
263
                        "tracking_total_items":           "tracking_total_items",
2,171✔
264
                        "tracking_total_prefixes":        "tracking_total_prefixes",
2,171✔
265

2,171✔
266
                        // # Replication
2,171✔
267
                        "connected_slaves":               "connected_slaves",
2,171✔
268
                        "repl_backlog_size":              "replication_backlog_bytes",
2,171✔
269
                        "repl_backlog_active":            "repl_backlog_is_active",
2,171✔
270
                        "repl_backlog_first_byte_offset": "repl_backlog_first_byte_offset",
2,171✔
271
                        "repl_backlog_histlen":           "repl_backlog_history_bytes",
2,171✔
272
                        "master_repl_offset":             "master_repl_offset",
2,171✔
273
                        "second_repl_offset":             "second_repl_offset",
2,171✔
274
                        "slave_expires_tracked_keys":     "slave_expires_tracked_keys",
2,171✔
275
                        "slave_priority":                 "slave_priority",
2,171✔
276
                        "sync_full":                      "replica_resyncs_full",
2,171✔
277
                        "sync_partial_ok":                "replica_partial_resync_accepted",
2,171✔
278
                        "sync_partial_err":               "replica_partial_resync_denied",
2,171✔
279

2,171✔
280
                        // # Cluster
2,171✔
281
                        "cluster_stats_messages_sent":     "cluster_messages_sent_total",
2,171✔
282
                        "cluster_stats_messages_received": "cluster_messages_received_total",
2,171✔
283

2,171✔
284
                        // # Tile38
2,171✔
285
                        // based on https://tile38.com/commands/server/
2,171✔
286
                        "tile38_aof_size":             "tile38_aof_size_bytes",
2,171✔
287
                        "tile38_avg_point_size":       "tile38_avg_item_size_bytes",
2,171✔
288
                        "tile38_sys_cpus":             "tile38_cpus_total",
2,171✔
289
                        "tile38_heap_released_bytes":  "tile38_heap_released_bytes",
2,171✔
290
                        "tile38_heap_alloc_bytes":     "tile38_heap_size_bytes",
2,171✔
291
                        "tile38_http_transport":       "tile38_http_transport",
2,171✔
292
                        "tile38_in_memory_size":       "tile38_in_memory_size_bytes",
2,171✔
293
                        "tile38_max_heap_size":        "tile38_max_heap_size_bytes",
2,171✔
294
                        "tile38_alloc_bytes":          "tile38_mem_alloc_bytes",
2,171✔
295
                        "tile38_num_collections":      "tile38_num_collections_total",
2,171✔
296
                        "tile38_num_hooks":            "tile38_num_hooks_total",
2,171✔
297
                        "tile38_num_objects":          "tile38_num_objects_total",
2,171✔
298
                        "tile38_num_points":           "tile38_num_points_total",
2,171✔
299
                        "tile38_pointer_size":         "tile38_pointer_size_bytes",
2,171✔
300
                        "tile38_read_only":            "tile38_read_only",
2,171✔
301
                        "tile38_go_threads":           "tile38_threads_total",
2,171✔
302
                        "tile38_go_goroutines":        "tile38_go_goroutines_total",
2,171✔
303
                        "tile38_last_gc_time_seconds": "tile38_last_gc_time_seconds",
2,171✔
304
                        "tile38_next_gc_bytes":        "tile38_next_gc_bytes",
2,171✔
305

2,171✔
306
                        // addtl. KeyDB metrics
2,171✔
307
                        "server_threads":        "server_threads_total",
2,171✔
308
                        "long_lock_waits":       "long_lock_waits_total",
2,171✔
309
                        "current_client_thread": "current_client_thread",
2,171✔
310

2,171✔
311
                        // Redis Modules metrics, RediSearch module
2,171✔
312
                        "search_number_of_indexes":   "search_number_of_indexes",
2,171✔
313
                        "search_used_memory_indexes": "search_used_memory_indexes_bytes",
2,171✔
314
                        "search_global_idle":         "search_global_idle",
2,171✔
315
                        "search_global_total":        "search_global_total",
2,171✔
316
                        "search_bytes_collected":     "search_collected_bytes",
2,171✔
317
                        "search_dialect_1":           "search_dialect_1",
2,171✔
318
                        "search_dialect_2":           "search_dialect_2",
2,171✔
319
                        "search_dialect_3":           "search_dialect_3",
2,171✔
320
                        "search_dialect_4":           "search_dialect_4",
2,171✔
321
                        // RediSearch module v8.0
2,171✔
322
                        "search_number_of_active_indexes":                 "search_number_of_active_indexes",
2,171✔
323
                        "search_number_of_active_indexes_running_queries": "search_number_of_active_indexes_running_queries",
2,171✔
324
                        "search_number_of_active_indexes_indexing":        "search_number_of_active_indexes_indexing",
2,171✔
325
                        "search_total_active_write_threads":               "search_total_active_write_threads",
2,171✔
326
                        "search_smallest_memory_index":                    "search_smallest_memory_index_bytes",
2,171✔
327
                        "search_largest_memory_index":                     "search_largest_memory_index_bytes",
2,171✔
328
                        "search_used_memory_vector_index":                 "search_used_memory_vector_index_bytes",
2,171✔
329
                        "search_global_idle_user":                         "search_global_idle_user",     // search_gc metrics were split into user and internal
2,171✔
330
                        "search_global_idle_internal":                     "search_global_idle_internal", // in PR: https://github.com/RediSearch/RediSearch/pull/5616
2,171✔
331
                        "search_global_total_user":                        "search_global_total_user",
2,171✔
332
                        "search_global_total_internal":                    "search_global_total_internal",
2,171✔
333
                        "search_gc_bytes_collected":                       "search_gc_collected_bytes", // search_bytes_collected was renamed in https://github.com/RediSearch/RediSearch/pull/5616
2,171✔
334
                        "search_gc_total_docs_not_collected":              "search_gc_total_docs_not_collected",
2,171✔
335
                        "search_gc_marked_deleted_vectors":                "search_gc_marked_deleted_vectors",
2,171✔
336
                        "search_errors_indexing_failures":                 "search_errors_indexing_failures",
2,171✔
337
                },
2,171✔
338

2,171✔
339
                metricMapCounters: map[string]string{
2,171✔
340
                        "total_connections_received": "connections_received_total",
2,171✔
341
                        "total_commands_processed":   "commands_processed_total",
2,171✔
342

2,171✔
343
                        "rejected_connections":   "rejected_connections_total",
2,171✔
344
                        "total_net_input_bytes":  "net_input_bytes_total",
2,171✔
345
                        "total_net_output_bytes": "net_output_bytes_total",
2,171✔
346

2,171✔
347
                        "total_net_repl_input_bytes":  "net_repl_input_bytes_total",
2,171✔
348
                        "total_net_repl_output_bytes": "net_repl_output_bytes_total",
2,171✔
349

2,171✔
350
                        "expired_subkeys":                "expired_subkeys_total",
2,171✔
351
                        "expired_keys":                   "expired_keys_total",
2,171✔
352
                        "expired_time_cap_reached_count": "expired_time_cap_reached_total",
2,171✔
353
                        "expire_cycle_cpu_milliseconds":  "expire_cycle_cpu_time_ms_total",
2,171✔
354
                        "evicted_keys":                   "evicted_keys_total",
2,171✔
355
                        "evicted_clients":                "evicted_clients_total", // Added in Redis 7.0
2,171✔
356
                        "evicted_scripts":                "evicted_scripts_total", // Added in Redis 7.4
2,171✔
357
                        "total_eviction_exceeded_time":   "eviction_exceeded_time_ms_total",
2,171✔
358
                        "keyspace_hits":                  "keyspace_hits_total",
2,171✔
359
                        "keyspace_misses":                "keyspace_misses_total",
2,171✔
360

2,171✔
361
                        "used_cpu_sys":              "cpu_sys_seconds_total",
2,171✔
362
                        "used_cpu_user":             "cpu_user_seconds_total",
2,171✔
363
                        "used_cpu_sys_children":     "cpu_sys_children_seconds_total",
2,171✔
364
                        "used_cpu_user_children":    "cpu_user_children_seconds_total",
2,171✔
365
                        "used_cpu_sys_main_thread":  "cpu_sys_main_thread_seconds_total",
2,171✔
366
                        "used_cpu_user_main_thread": "cpu_user_main_thread_seconds_total",
2,171✔
367

2,171✔
368
                        "unexpected_error_replies":                  "unexpected_error_replies",
2,171✔
369
                        "total_error_replies":                       "total_error_replies",
2,171✔
370
                        "dump_payload_sanitizations":                "dump_payload_sanitizations",
2,171✔
371
                        "total_reads_processed":                     "total_reads_processed",
2,171✔
372
                        "total_writes_processed":                    "total_writes_processed",
2,171✔
373
                        "io_threaded_reads_processed":               "io_threaded_reads_processed",
2,171✔
374
                        "io_threaded_writes_processed":              "io_threaded_writes_processed",
2,171✔
375
                        "client_query_buffer_limit_disconnections":  "client_query_buffer_limit_disconnections_total",
2,171✔
376
                        "client_output_buffer_limit_disconnections": "client_output_buffer_limit_disconnections_total",
2,171✔
377
                        "reply_buffer_shrinks":                      "reply_buffer_shrinks_total",
2,171✔
378
                        "reply_buffer_expands":                      "reply_buffer_expands_total",
2,171✔
379
                        "acl_access_denied_auth":                    "acl_access_denied_auth_total",
2,171✔
380
                        "acl_access_denied_cmd":                     "acl_access_denied_cmd_total",
2,171✔
381
                        "acl_access_denied_key":                     "acl_access_denied_key_total",
2,171✔
382
                        "acl_access_denied_channel":                 "acl_access_denied_channel_total",
2,171✔
383

2,171✔
384
                        // addtl. KeyDB metrics
2,171✔
385
                        "cached_keys":                  "cached_keys_total",
2,171✔
386
                        "storage_provider_read_hits":   "storage_provider_read_hits",
2,171✔
387
                        "storage_provider_read_misses": "storage_provider_read_misses",
2,171✔
388

2,171✔
389
                        // Redis Modules metrics, RediSearch module
2,171✔
390
                        "search_total_indexing_time": "search_indexing_time_ms_total",
2,171✔
391
                        "search_total_cycles":        "search_cycles_total",
2,171✔
392
                        "search_total_ms_run":        "search_run_ms_total",
2,171✔
393
                        // RediSearch module v8.0
2,171✔
394
                        "search_gc_total_cycles":               "search_gc_cycles_total", // search_gc metrics were renamed
2,171✔
395
                        "search_gc_total_ms_run":               "search_gc_run_ms_total", // in PR: https://github.com/RediSearch/RediSearch/pull/5616
2,171✔
396
                        "search_total_queries_processed":       "search_queries_processed_total",
2,171✔
397
                        "search_total_query_commands":          "search_query_commands_total",
2,171✔
398
                        "search_total_query_execution_time_ms": "search_query_execution_time_ms_total",
2,171✔
399
                        "search_total_active_queries":          "search_active_queries_total",
2,171✔
400
                },
2,171✔
401
        }
2,171✔
402

2,171✔
403
        if e.options.ConfigCommandName == "" {
2,331✔
404
                e.options.ConfigCommandName = "CONFIG"
160✔
405
        }
160✔
406

407
        if keys, err := parseKeyArg(opts.CheckKeys); err != nil {
2,172✔
408
                return nil, fmt.Errorf("couldn't parse check-keys: %s", err)
1✔
409
        } else {
2,171✔
410
                slog.Debug("keys", "keys", keys)
2,170✔
411
        }
2,170✔
412

413
        if singleKeys, err := parseKeyArg(opts.CheckSingleKeys); err != nil {
2,171✔
414
                return nil, fmt.Errorf("couldn't parse check-single-keys: %s", err)
1✔
415
        } else {
2,170✔
416
                slog.Debug("singleKeys", "singleKeys", singleKeys)
2,169✔
417
        }
2,169✔
418

419
        if streams, err := parseKeyArg(opts.CheckStreams); err != nil {
2,170✔
420
                return nil, fmt.Errorf("couldn't parse check-streams: %s", err)
1✔
421
        } else {
2,169✔
422
                slog.Debug("streams", "streams", streams)
2,168✔
423
        }
2,168✔
424

425
        if singleStreams, err := parseKeyArg(opts.CheckSingleStreams); err != nil {
2,168✔
426
                return nil, fmt.Errorf("couldn't parse check-single-streams: %s", err)
×
427
        } else {
2,168✔
428
                slog.Debug("singleStreams", "singleStreams", singleStreams)
2,168✔
429
        }
2,168✔
430

431
        if countKeys, err := parseKeyArg(opts.CountKeys); err != nil {
2,168✔
432
                return nil, fmt.Errorf("couldn't parse count-keys: %s", err)
×
433
        } else {
2,168✔
434
                slog.Debug("countKeys", "countKeys", countKeys)
2,168✔
435
        }
2,168✔
436

437
        if opts.InclSystemMetrics {
2,169✔
438
                e.metricMapGauges["total_system_memory"] = "total_system_memory_bytes"
1✔
439
        }
1✔
440

441
        e.metricDescriptions = map[string]*prometheus.Desc{}
2,168✔
442

2,168✔
443
        for k, desc := range map[string]struct {
2,168✔
444
                txt  string
2,168✔
445
                lbls []string
2,168✔
446
        }{
2,168✔
447
                "commands_duration_seconds_total":                    {txt: `Total amount of time in seconds spent per command`, lbls: []string{"cmd"}},
2,168✔
448
                "commands_failed_calls_total":                        {txt: `Total number of errors prior command execution per command`, lbls: []string{"cmd"}},
2,168✔
449
                "commands_latencies_usec":                            {txt: `A histogram of latencies per command`, lbls: []string{"cmd"}},
2,168✔
450
                "commands_rejected_calls_total":                      {txt: `Total number of errors within command execution per command`, lbls: []string{"cmd"}},
2,168✔
451
                "commands_total":                                     {txt: `Total number of calls per command`, lbls: []string{"cmd"}},
2,168✔
452
                "config_client_output_buffer_limit_bytes":            {txt: `The configured buffer limits per class`, lbls: []string{"class", "limit"}},
2,168✔
453
                "config_client_output_buffer_limit_overcome_seconds": {txt: `How long for buffer limits per class to be exceeded before replicas are dropped`, lbls: []string{"class", "limit"}},
2,168✔
454
                "config_key_value":                                   {txt: `Config key and value`, lbls: []string{"key", "value"}},
2,168✔
455
                "config_value":                                       {txt: `Config key and value as metric`, lbls: []string{"key"}},
2,168✔
456
                "connected_slave_lag_seconds":                        {txt: "Lag of connected slave", lbls: []string{"slave_ip", "slave_port", "slave_state"}},
2,168✔
457
                "connected_slave_offset_bytes":                       {txt: "Offset of connected slave", lbls: []string{"slave_ip", "slave_port", "slave_state"}},
2,168✔
458
                "db_avg_ttl_seconds":                                 {txt: "Avg TTL in seconds", lbls: []string{"db"}},
2,168✔
459
                "db_keys":                                            {txt: "Total number of keys by DB", lbls: []string{"db"}},
2,168✔
460
                "db_keys_cached":                                     {txt: "Total number of cached keys by DB", lbls: []string{"db"}},
2,168✔
461
                "db_keys_expiring":                                   {txt: "Total number of expiring keys by DB", lbls: []string{"db"}},
2,168✔
462
                "errors_total":                                       {txt: `Total number of errors per error type`, lbls: []string{"err"}},
2,168✔
463
                "exporter_last_scrape_error":                         {txt: "The last scrape error status.", lbls: []string{"err"}},
2,168✔
464
                "key_group_count":                                    {txt: `Count of keys in key group`, lbls: []string{"db", "key_group"}},
2,168✔
465
                "key_group_memory_usage_bytes":                       {txt: `Total memory usage of key group in bytes`, lbls: []string{"db", "key_group"}},
2,168✔
466
                "key_memory_usage_bytes":                             {txt: `The memory usage of "key" in bytes`, lbls: []string{"db", "key"}},
2,168✔
467
                "key_size":                                           {txt: `The length or size of "key"`, lbls: []string{"db", "key"}},
2,168✔
468
                "key_value":                                          {txt: `The value of "key"`, lbls: []string{"db", "key"}},
2,168✔
469
                "key_value_as_string":                                {txt: `The value of "key" as a string`, lbls: []string{"db", "key", "val"}},
2,168✔
470
                "keys_count":                                         {txt: `Count of keys`, lbls: []string{"db", "key"}},
2,168✔
471
                "last_key_groups_scrape_duration_milliseconds":       {txt: `Duration of the last key group metrics scrape in milliseconds`},
2,168✔
472
                "last_slow_execution_duration_seconds":               {txt: `The amount of time needed for last slow execution, in seconds`},
2,168✔
473
                "latency_percentiles_usec":                           {txt: `A summary of latency percentile distribution per command`, lbls: []string{"cmd"}},
2,168✔
474
                "latency_spike_duration_seconds":                     {txt: `Length of the last latency spike in seconds`, lbls: []string{"event_name"}},
2,168✔
475
                "latency_spike_last":                                 {txt: `When the latency spike last occurred`, lbls: []string{"event_name"}},
2,168✔
476
                "master_last_io_seconds_ago":                         {txt: "Master last io seconds ago", lbls: []string{"master_host", "master_port"}},
2,168✔
477
                "master_link_up":                                     {txt: "Master link status on Redis slave", lbls: []string{"master_host", "master_port"}},
2,168✔
478
                "master_sync_in_progress":                            {txt: "Master sync in progress", lbls: []string{"master_host", "master_port"}},
2,168✔
479
                "module_info":                                        {txt: "Information about loaded Redis module", lbls: []string{"name", "ver", "api", "filters", "usedby", "using"}},
2,168✔
480
                "number_of_distinct_key_groups":                      {txt: `Number of distinct key groups`, lbls: []string{"db"}},
2,168✔
481
                "script_result":                                      {txt: "Result of the collect script evaluation", lbls: []string{"filename"}},
2,168✔
482
                "script_values":                                      {txt: "Values returned by the collect script", lbls: []string{"key", "filename"}},
2,168✔
483
                "sentinel_master_ckquorum_status":                    {txt: "Master ckquorum status", lbls: []string{"master_name", "message"}},
2,168✔
484
                "sentinel_master_ok_sentinels":                       {txt: "The number of okay sentinels monitoring this master", lbls: []string{"master_name", "master_address"}},
2,168✔
485
                "sentinel_master_ok_slaves":                          {txt: "The number of okay slaves of the master", lbls: []string{"master_name", "master_address"}},
2,168✔
486
                "sentinel_master_sentinels":                          {txt: "The number of sentinels monitoring this master", lbls: []string{"master_name", "master_address"}},
2,168✔
487
                "sentinel_master_setting_ckquorum":                   {txt: "Show the current ckquorum config for each master", lbls: []string{"master_name", "master_address"}},
2,168✔
488
                "sentinel_master_setting_down_after_milliseconds":    {txt: "Show the current down-after-milliseconds config for each master", lbls: []string{"master_name", "master_address"}},
2,168✔
489
                "sentinel_master_setting_failover_timeout":           {txt: "Show the current failover-timeout config for each master", lbls: []string{"master_name", "master_address"}},
2,168✔
490
                "sentinel_master_setting_parallel_syncs":             {txt: "Show the current parallel-syncs config for each master", lbls: []string{"master_name", "master_address"}},
2,168✔
491
                "sentinel_master_slaves":                             {txt: "The number of slaves of the master", lbls: []string{"master_name", "master_address"}},
2,168✔
492
                "sentinel_master_status":                             {txt: "Master status on Sentinel", lbls: []string{"master_name", "master_address", "master_status"}},
2,168✔
493
                "sentinel_masters":                                   {txt: "The number of masters this sentinel is watching"},
2,168✔
494
                "sentinel_running_scripts":                           {txt: "Number of scripts in execution right now"},
2,168✔
495
                "sentinel_scripts_queue_length":                      {txt: "Queue of user scripts to execute"},
2,168✔
496
                "sentinel_simulate_failure_flags":                    {txt: "Failures simulations"},
2,168✔
497
                "sentinel_tilt":                                      {txt: "Sentinel is in TILT mode"},
2,168✔
498
                "slave_info":                                         {txt: "Information about the Redis slave", lbls: []string{"master_host", "master_port", "read_only"}},
2,168✔
499
                "slave_repl_offset":                                  {txt: "Slave replication offset", lbls: []string{"master_host", "master_port"}},
2,168✔
500
                "slowlog_last_id":                                    {txt: `Last id of slowlog`},
2,168✔
501
                "slowlog_length":                                     {txt: `Total slowlog`},
2,168✔
502
                "start_time_seconds":                                 {txt: "Start time of the Redis instance since unix epoch in seconds."},
2,168✔
503
                "stream_first_entry_id":                              {txt: `The epoch timestamp (ms) of the first message in the stream`, lbls: []string{"db", "stream"}},
2,168✔
504
                "stream_group_consumer_idle_seconds":                 {txt: `Consumer idle time in seconds`, lbls: []string{"db", "stream", "group", "consumer"}},
2,168✔
505
                "stream_group_consumer_messages_pending":             {txt: `Pending number of messages for this specific consumer`, lbls: []string{"db", "stream", "group", "consumer"}},
2,168✔
506
                "stream_group_consumers":                             {txt: `Consumers count of stream group`, lbls: []string{"db", "stream", "group"}},
2,168✔
507
                "stream_group_entries_read":                          {txt: `Total number of entries read from the stream group`, lbls: []string{"db", "stream", "group"}},
2,168✔
508
                "stream_group_lag":                                   {txt: `The number of messages waiting to be delivered to the stream group's consumers`, lbls: []string{"db", "stream", "group"}},
2,168✔
509
                "stream_group_last_delivered_id":                     {txt: `The epoch timestamp (ms) of the last delivered message`, lbls: []string{"db", "stream", "group"}},
2,168✔
510
                "stream_group_messages_pending":                      {txt: `Pending number of messages in that stream group`, lbls: []string{"db", "stream", "group"}},
2,168✔
511
                "stream_groups":                                      {txt: `Groups count of stream`, lbls: []string{"db", "stream"}},
2,168✔
512
                "stream_last_entry_id":                               {txt: `The epoch timestamp (ms) of the last message in the stream`, lbls: []string{"db", "stream"}},
2,168✔
513
                "stream_last_generated_id":                           {txt: `The epoch timestamp (ms) of the latest message on the stream`, lbls: []string{"db", "stream"}},
2,168✔
514
                "stream_length":                                      {txt: `The number of elements of the stream`, lbls: []string{"db", "stream"}},
2,168✔
515
                "stream_max_deleted_entry_id":                        {txt: `The epoch timestamp (ms) of last message was deleted from the stream`, lbls: []string{"db", "stream"}},
2,168✔
516
                "stream_radix_tree_keys":                             {txt: `Radix tree keys count"`, lbls: []string{"db", "stream"}},
2,168✔
517
                "stream_radix_tree_nodes":                            {txt: `Radix tree nodes count`, lbls: []string{"db", "stream"}},
2,168✔
518
                "up":                                                 {txt: "Information about the Redis instance"},
2,168✔
519
        } {
158,264✔
520
                e.metricDescriptions[k] = newMetricDescr(opts.Namespace, k, desc.txt, desc.lbls)
156,096✔
521
        }
156,096✔
522

523
        if e.options.MetricsPath == "" {
2,328✔
524
                e.options.MetricsPath = "/metrics"
160✔
525
        }
160✔
526

527
        e.mux = http.NewServeMux()
2,168✔
528

2,168✔
529
        if e.options.Registry != nil {
4,336✔
530
                e.options.Registry.MustRegister(e)
2,168✔
531
                e.mux.Handle(e.options.MetricsPath, promhttp.HandlerFor(
2,168✔
532
                        e.options.Registry, promhttp.HandlerOpts{ErrorHandling: promhttp.ContinueOnError},
2,168✔
533
                ))
2,168✔
534

2,168✔
535
                if !e.options.RedisMetricsOnly {
4,335✔
536
                        buildInfoCollector := prometheus.NewGaugeVec(prometheus.GaugeOpts{
2,167✔
537
                                Namespace: opts.Namespace,
2,167✔
538
                                Name:      "exporter_build_info",
2,167✔
539
                                Help:      "redis exporter build_info",
2,167✔
540
                        }, []string{"version", "commit_sha", "build_date", "golang_version"})
2,167✔
541
                        buildInfoCollector.WithLabelValues(e.buildInfo.Version, e.buildInfo.CommitSha, e.buildInfo.Date, runtime.Version()).Set(1)
2,167✔
542
                        e.options.Registry.MustRegister(buildInfoCollector)
2,167✔
543
                }
2,167✔
544
        }
545

546
        e.mux.HandleFunc("/", e.indexHandler)
2,168✔
547
        e.mux.HandleFunc("/scrape", e.scrapeHandler)
2,168✔
548
        e.mux.HandleFunc("/discover-cluster-nodes", e.discoverClusterNodesHandler)
2,168✔
549
        e.mux.HandleFunc("/health", e.healthHandler)
2,168✔
550
        e.mux.HandleFunc("/-/reload", e.reloadPwdFile)
2,168✔
551

2,168✔
552
        return e, nil
2,168✔
553
}
554

555
// Describe outputs Redis metric descriptions.
556
func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {
2,168✔
557
        for _, desc := range e.metricDescriptions {
158,264✔
558
                ch <- desc
156,096✔
559
        }
156,096✔
560

561
        for _, v := range e.metricMapGauges {
355,553✔
562
                ch <- newMetricDescr(e.options.Namespace, v, v+" metric", nil)
353,385✔
563
        }
353,385✔
564

565
        for _, v := range e.metricMapCounters {
110,568✔
566
                ch <- newMetricDescr(e.options.Namespace, v, v+" metric", nil)
108,400✔
567
        }
108,400✔
568

569
        ch <- e.totalScrapes.Desc()
2,168✔
570
        ch <- e.scrapeDuration.Desc()
2,168✔
571
        ch <- e.targetScrapeRequestErrors.Desc()
2,168✔
572
}
573

574
func ParseLogLevel(level string) (slog.Level, error) {
1✔
575
        switch strings.ToUpper(level) {
1✔
NEW
576
        case "DEBUG":
×
NEW
577
                return slog.LevelDebug, nil
×
578
        case "INFO":
1✔
579
                return slog.LevelInfo, nil
1✔
NEW
580
        case "WARN", "WARNING":
×
NEW
581
                return slog.LevelWarn, nil
×
NEW
582
        case "ERROR":
×
NEW
583
                return slog.LevelError, nil
×
NEW
584
        default:
×
NEW
585
                return slog.LevelInfo, fmt.Errorf("invalid log level: %s", level)
×
586
        }
587
}
588

589
// Collect fetches new metrics from the RedisHost and updates the appropriate metrics.
590
func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
2,096✔
591
        e.Lock()
2,096✔
592
        defer e.Unlock()
2,096✔
593
        e.totalScrapes.Inc()
2,096✔
594

2,096✔
595
        if e.redisAddr != "" {
4,192✔
596
                startTime := time.Now()
2,096✔
597
                var up float64
2,096✔
598
                if err := e.scrapeRedisHost(ch); err != nil {
2,101✔
599
                        e.registerConstMetricGauge(ch, "exporter_last_scrape_error", 1.0, fmt.Sprintf("%s", err))
5✔
600
                } else {
2,096✔
601
                        up = 1
2,091✔
602
                        e.registerConstMetricGauge(ch, "exporter_last_scrape_error", 0, "")
2,091✔
603
                }
2,091✔
604

605
                e.registerConstMetricGauge(ch, "up", up)
2,096✔
606

2,096✔
607
                took := time.Since(startTime).Seconds()
2,096✔
608
                e.scrapeDuration.Observe(took)
2,096✔
609
                e.registerConstMetricGauge(ch, "exporter_last_scrape_duration_seconds", took)
2,096✔
610
        }
611

612
        ch <- e.totalScrapes
2,096✔
613
        ch <- e.scrapeDuration
2,096✔
614
        ch <- e.targetScrapeRequestErrors
2,096✔
615
}
616

617
func (e *Exporter) extractConfigMetrics(ch chan<- prometheus.Metric, config []interface{}) (dbCount int, err error) {
2,088✔
618
        if len(config)%2 != 0 {
2,088✔
619
                return 0, fmt.Errorf("invalid config: %#v", config)
×
620
        }
×
621

622
        for pos := 0; pos < len(config)/2; pos++ {
363,255✔
623
                strKey, err := redis.String(config[pos*2], nil)
361,167✔
624
                if err != nil {
361,167✔
NEW
625
                        slog.Error("invalid config key name, skipped", "error", err)
×
626
                        continue
×
627
                }
628

629
                strVal, err := redis.String(config[pos*2+1], nil)
361,167✔
630
                if err != nil {
361,445✔
631
                        slog.Debug("invalid config value for key name, skipped", "key", strKey, "error", err)
278✔
632
                        continue
278✔
633
                }
634

635
                if strKey == "databases" {
362,802✔
636
                        if dbCount, err = strconv.Atoi(strVal); err != nil {
1,913✔
637
                                return 0, fmt.Errorf("invalid config value for key databases: %#v", strVal)
×
638
                        }
×
639
                }
640

641
                if e.options.InclConfigMetrics {
361,116✔
642
                        if redact := map[string]bool{
227✔
643
                                "masterauth":               true,
227✔
644
                                "requirepass":              true,
227✔
645
                                "tls-key-file-pass":        true,
227✔
646
                                "tls-client-key-file-pass": true,
227✔
647
                        }[strKey]; !redact || !e.options.RedactConfigMetrics {
454✔
648
                                e.registerConstMetricGauge(ch, "config_key_value", 1.0, strKey, strVal)
227✔
649
                                if val, err := strconv.ParseFloat(strVal, 64); err == nil {
321✔
650
                                        e.registerConstMetricGauge(ch, "config_value", val, strKey)
94✔
651
                                }
94✔
652
                        }
653
                }
654

655
                if map[string]bool{
360,889✔
656
                        "io-threads": true,
360,889✔
657
                        "maxclients": true,
360,889✔
658
                        "maxmemory":  true,
360,889✔
659
                }[strKey] {
366,199✔
660
                        if val, err := strconv.ParseFloat(strVal, 64); err == nil {
10,445✔
661
                                strKey = strings.ReplaceAll(strKey, "-", "_")
5,135✔
662
                                e.registerConstMetricGauge(ch, fmt.Sprintf("config_%s", strKey), val)
5,135✔
663
                        }
5,135✔
664
                }
665

666
                if strKey == "client-output-buffer-limit" {
362,802✔
667
                        // client-output-buffer-limit "normal 0 0 0 slave 1610612736 1610612736 0 pubsub 33554432 8388608 60"
1,913✔
668
                        splitVal := strings.Split(strVal, " ")
1,913✔
669
                        for i := 0; i < len(splitVal); i += 4 {
7,652✔
670
                                class := splitVal[i]
5,739✔
671
                                if val, err := strconv.ParseFloat(splitVal[i+1], 64); err == nil {
11,478✔
672
                                        e.registerConstMetricGauge(ch, "config_client_output_buffer_limit_bytes", val, class, "hard")
5,739✔
673
                                }
5,739✔
674
                                if val, err := strconv.ParseFloat(splitVal[i+2], 64); err == nil {
11,478✔
675
                                        e.registerConstMetricGauge(ch, "config_client_output_buffer_limit_bytes", val, class, "soft")
5,739✔
676
                                }
5,739✔
677
                                if val, err := strconv.ParseFloat(splitVal[i+3], 64); err == nil {
11,478✔
678
                                        e.registerConstMetricGauge(ch, "config_client_output_buffer_limit_overcome_seconds", val, class, "soft")
5,739✔
679
                                }
5,739✔
680
                        }
681
                }
682
        }
683
        return
2,088✔
684
}
685

686
// getKeyOperationConnection returns the appropriate Redis connection for key-based operations.
687
// For cluster mode, it returns a cluster connection; otherwise, it returns the provided connection.
688
func (e *Exporter) getKeyOperationConnection(defaultConn redis.Conn) (redis.Conn, error) {
4,187✔
689
        if e.options.IsCluster {
4,202✔
690
                return e.connectToRedisCluster()
15✔
691
        }
15✔
692
        return defaultConn, nil
4,172✔
693
}
694

695
func (e *Exporter) scrapeRedisHost(ch chan<- prometheus.Metric) error {
2,097✔
696
        defer slog.Debug("Finished scraping Redis host", "address", e.redisAddr)
2,097✔
697

2,097✔
698
        startTime := time.Now()
2,097✔
699
        c, err := e.connectToRedis()
2,097✔
700
        connectTookSeconds := time.Since(startTime).Seconds()
2,097✔
701
        e.registerConstMetricGauge(ch, "exporter_last_scrape_connect_time_seconds", connectTookSeconds)
2,097✔
702

2,097✔
703
        if err != nil {
2,100✔
704
                var redactedAddr string
3✔
705
                if redisURL, err2 := url.Parse(e.redisAddr); err2 != nil {
3✔
NEW
706
                        slog.Debug("Failed to parse URL", "address", e.redisAddr, "error", err2)
×
707
                        redactedAddr = "<redacted>"
×
708
                } else {
3✔
709
                        redactedAddr = redisURL.Redacted()
3✔
710
                }
3✔
711
                slog.Error("Couldn't connect to redis instance", "address", redactedAddr)
3✔
712
                slog.Debug("Connect to redis failed", "address", e.redisAddr, "error", err)
3✔
713
                return err
3✔
714
        }
715
        defer c.Close()
2,094✔
716

2,094✔
717
        slog.Debug("Connected to redis", "address", e.redisAddr)
2,094✔
718
        slog.Debug("connecting took seconds", "seconds", connectTookSeconds)
2,094✔
719

2,094✔
720
        if e.options.PingOnConnect {
2,095✔
721
                startTime := time.Now()
1✔
722

1✔
723
                if _, err := doRedisCmd(c, "PING"); err != nil {
1✔
NEW
724
                        slog.Error("Couldn't PING server", "error", err)
×
725
                } else {
1✔
726
                        pingTookSeconds := time.Since(startTime).Seconds()
1✔
727
                        e.registerConstMetricGauge(ch, "exporter_last_scrape_ping_time_seconds", pingTookSeconds)
1✔
728
                        slog.Debug("PING took seconds", "seconds", pingTookSeconds)
1✔
729
                }
1✔
730
        }
731

732
        if e.options.SetClientName {
2,094✔
733
                if _, err := doRedisCmd(c, "CLIENT", "SETNAME", "redis_exporter"); err != nil {
×
NEW
734
                        slog.Error("Couldn't set client name", "error", err)
×
735
                }
×
736
        }
737

738
        dbCount := 0
2,094✔
739
        if e.options.ConfigCommandName == "-" {
2,096✔
740
                slog.Debug("Skipping config metrics extraction")
2✔
741
        } else {
2,094✔
742
                if config, err := redis.Values(doRedisCmd(c, e.options.ConfigCommandName, "GET", "*")); err == nil {
4,180✔
743
                        dbCount, err = e.extractConfigMetrics(ch, config)
2,088✔
744
                        if err != nil {
2,088✔
NEW
745
                                slog.Error("Failed to extract config metrics", "error", err)
×
746
                                return err
×
747
                        }
×
748
                } else {
4✔
749
                        slog.Debug("Redis CONFIG err", "error", err)
4✔
750
                }
4✔
751
        }
752

753
        infoAll, err := redis.String(doRedisCmd(c, "INFO", "ALL"))
2,094✔
754
        if err != nil || infoAll == "" {
2,094✔
NEW
755
                slog.Debug("Redis INFO ALL err", "error", err)
×
756
                infoAll, err = redis.String(doRedisCmd(c, "INFO"))
×
757
                if err != nil {
×
NEW
758
                        slog.Error("Redis INFO err", "error", err)
×
759
                        return err
×
760
                }
×
761
        }
762
        slog.Debug("Redis INFO ALL result", "result", infoAll)
2,094✔
763

2,094✔
764
        if strings.Contains(infoAll, "cluster_enabled:1") {
2,415✔
765
                if clusterInfo, err := redis.String(doRedisCmd(c, "CLUSTER", "INFO")); err == nil {
642✔
766
                        e.extractClusterInfoMetrics(ch, clusterInfo)
321✔
767

321✔
768
                        // in cluster mode, Redis only supports one database, so no extra DB number padding needed
321✔
769
                        dbCount = 1
321✔
770
                } else {
321✔
NEW
771
                        slog.Error("Redis CLUSTER INFO err", "error", err)
×
772
                }
×
773
        } else if dbCount == 0 {
1,954✔
774
                // in non-cluster mode, if dbCount is zero, then "CONFIG" failed to retrieve a valid
181✔
775
                // number of databases, and we use the Redis config default which is 16
181✔
776

181✔
777
                dbCount = 16
181✔
778
        }
181✔
779

780
        slog.Debug("dbCount", "count", dbCount)
2,094✔
781

2,094✔
782
        role := e.extractInfoMetrics(ch, infoAll, dbCount)
2,094✔
783

2,094✔
784
        if !e.options.ExcludeLatencyHistogramMetrics {
4,188✔
785
                e.extractLatencyMetrics(ch, infoAll, c)
2,094✔
786
        }
2,094✔
787

788
        // skip these metrics for master if SkipCheckKeysForRoleMaster is set
789
        // (can help with reducing workload on the master node)
790
        slog.Debug("checkKeys metric collection for role", "role", role, "SkipCheckKeysForRoleMaster", e.options.SkipCheckKeysForRoleMaster)
2,094✔
791

2,094✔
792
        if role == InstanceRoleSlave || !e.options.SkipCheckKeysForRoleMaster {
4,187✔
793
                // For key-based operations, use cluster connection if in cluster mode
2,093✔
794
                keyConn, err := e.getKeyOperationConnection(c)
2,093✔
795
                if err != nil {
2,093✔
NEW
796
                        slog.Error("failed to get key operation connection", "error", err)
×
797
                } else {
2,093✔
798
                        defer func() {
4,186✔
799
                                if keyConn != c {
2,100✔
800
                                        keyConn.Close()
7✔
801
                                }
7✔
802
                        }()
803

804
                        if err := e.extractCheckKeyMetrics(ch, keyConn); err != nil {
2,093✔
NEW
805
                                slog.Error("extractCheckKeyMetrics()", "error", err)
×
806
                        }
×
807

808
                        e.extractCountKeysMetrics(ch, keyConn)
2,093✔
809

2,093✔
810
                        e.extractStreamMetrics(ch, keyConn)
2,093✔
811
                }
812
        } else {
1✔
813
                slog.Info("skipping checkKeys metrics", "role", role, "flag", e.options.SkipCheckKeysForRoleMaster)
1✔
814
        }
1✔
815
        e.extractSlowLogMetrics(ch, c)
2,094✔
816

2,094✔
817
        // Key groups also need cluster connection for key operations
2,094✔
818
        keyGroupConn, err := e.getKeyOperationConnection(c)
2,094✔
819
        if err != nil {
2,094✔
NEW
820
                slog.Error("failed to get key operation connection for key groups", "error", err)
×
821
        } else {
2,094✔
822
                defer func() {
4,188✔
823
                        if keyGroupConn != c {
2,102✔
824
                                keyGroupConn.Close()
8✔
825
                        }
8✔
826
                }()
827
                e.extractKeyGroupMetrics(ch, keyGroupConn, dbCount)
2,094✔
828
        }
829

830
        if strings.Contains(infoAll, "# Sentinel") {
2,095✔
831
                e.extractSentinelMetrics(ch, c)
1✔
832
        }
1✔
833

834
        if e.options.ExportClientList {
2,098✔
835
                e.extractConnectedClientMetrics(ch, c)
4✔
836
        }
4✔
837

838
        if e.options.IsTile38 {
2,096✔
839
                e.extractTile38Metrics(ch, c)
2✔
840
        }
2✔
841

842
        if e.options.InclModulesMetrics {
2,098✔
843
                e.extractModulesMetrics(ch, c)
4✔
844
        }
4✔
845

846
        if len(e.options.LuaScript) > 0 {
2,110✔
847
                for filename, script := range e.options.LuaScript {
32✔
848
                        if err := e.extractLuaScriptMetrics(ch, c, filename, script); err != nil {
18✔
849
                                return err
2✔
850
                        }
2✔
851
                }
852
        }
853

854
        return nil
2,092✔
855
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc