Skip to content

Commit

Permalink
Feature: Add redis search module metrics (#953)
Browse files Browse the repository at this point in the history
* Feature: Add redis search module metrics
  • Loading branch information
nantiferov authored Oct 5, 2024
1 parent 3ae7e68 commit 1f06f2f
Show file tree
Hide file tree
Showing 8 changed files with 158 additions and 1 deletion.
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ test:
TEST_REDIS_CLUSTER_PASSWORD_URI="redis://localhost:17006" \
TEST_TILE38_URI="redis://localhost:19851" \
TEST_REDIS_SENTINEL_URI="redis://localhost:26379" \
go test -v -covermode=atomic -cover -race -coverprofile=coverage.txt -p 1 ./...
TEST_REDIS_MODULES_URI="redis://localhost:36379" \
go test -v -covermode=atomic -cover -race -coverprofile=coverage.txt -p 1 ./...

.PHONY: lint
lint:
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ Prometheus uses file watches and all changes to the json file are applied immedi
| redis-only-metrics | REDIS_EXPORTER_REDIS_ONLY_METRICS | Whether to also export 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. |
| exclude-latency-histogram-metrics | REDIS_EXPORTER_EXCLUDE_LATENCY_HISTOGRAM_METRICS | Do not try to collect latency histogram metrics (to avoid `WARNING, LOGGED ONCE ONLY: cmd LATENCY HISTOGRAM` error on Redis < v7). |
| redact-config-metrics | REDIS_EXPORTER_REDACT_CONFIG_METRICS | Whether to redact config settings that include potentially sensitive information like passwords. |
| ping-on-connect | REDIS_EXPORTER_PING_ON_CONNECT | Whether to ping the redis instance after connecting and record the duration as a metric, defaults to false. |
Expand Down
5 changes: 5 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,8 @@ services:
image: tile38/tile38:latest
ports:
- "19851:9851"

redis-stack:
image: redis/redis-stack-server:7.4.0-v0
ports:
- "36379:6379"
21 changes: 21 additions & 0 deletions exporter/exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ type Options struct {
ClientKeyFile string
CaCertFile string
InclConfigMetrics bool
InclModulesMetrics bool
DisableExportingKeyValues bool
ExcludeLatencyHistogramMetrics bool
RedactConfigMetrics bool
Expand Down Expand Up @@ -267,6 +268,21 @@ func NewRedisExporter(redisURI string, opts Options) (*Exporter, error) {
"server_threads": "server_threads_total",
"long_lock_waits": "long_lock_waits_total",
"current_client_thread": "current_client_thread",

// Redis Modules metrics
// RediSearch module
"search_number_of_indexes": "search_number_of_indexes",
"search_used_memory_indexes": "search_used_memory_indexes_bytes",
"search_total_indexing_time": "search_total_indexing_time_ms",
"search_global_idle": "search_global_idle",
"search_global_total": "search_global_total",
"search_bytes_collected": "search_collected_bytes",
"search_total_cycles": "search_total_cycles",
"search_total_ms_run": "search_total_run_ms",
"search_dialect_1": "search_dialect_1",
"search_dialect_2": "search_dialect_2",
"search_dialect_3": "search_dialect_3",
"search_dialect_4": "search_dialect_4",
},

metricMapCounters: map[string]string{
Expand Down Expand Up @@ -421,6 +437,7 @@ func NewRedisExporter(redisURI string, opts Options) (*Exporter, error) {
"stream_radix_tree_keys": {txt: `Radix tree keys count"`, lbls: []string{"db", "stream"}},
"stream_radix_tree_nodes": {txt: `Radix tree nodes count`, lbls: []string{"db", "stream"}},
"up": {txt: "Information about the Redis instance"},
"module_info": {txt: "Information about loaded Redis module", lbls: []string{"name", "ver", "api", "filters", "usedby", "using"}},
} {
e.metricDescriptions[k] = newMetricDescr(opts.Namespace, k, desc.txt, desc.lbls)
}
Expand Down Expand Up @@ -698,6 +715,10 @@ func (e *Exporter) scrapeRedisHost(ch chan<- prometheus.Metric) error {
e.extractTile38Metrics(ch, c)
}

if e.options.InclModulesMetrics {
e.extractModulesMetrics(ch, c)
}

if len(e.options.LuaScript) > 0 {
for filename, script := range e.options.LuaScript {
if err := e.extractLuaScriptMetrics(ch, c, filename, script); err != nil {
Expand Down
1 change: 1 addition & 0 deletions exporter/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ func TestSimultaneousMetricsHttpRequests(t *testing.T) {

os.Getenv("TEST_REDIS5_URI"),
os.Getenv("TEST_REDIS6_URI"),
os.Getenv("TEST_REDIS_MODULES_URI"),

// tile38 & Cluster need to be last in this list so we can identify them when selected, down in line 229
os.Getenv("TEST_REDIS_CLUSTER_MASTER_URI"),
Expand Down
52 changes: 52 additions & 0 deletions exporter/modules.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package exporter

import (
"strings"

"github.com/gomodule/redigo/redis"
"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"
)

func (e *Exporter) extractModulesMetrics(ch chan<- prometheus.Metric, c redis.Conn) {
info, err := redis.String(doRedisCmd(c, "INFO", "MODULES"))
if err != nil {
log.Errorf("extractSearchMetrics() err: %s", err)
return
}

lines := strings.Split(info, "\r\n")
for _, line := range lines {
log.Debugf("info: %s", line)

split := strings.Split(line, ":")
if len(split) != 2 {
continue
}

if split[0] == "module" {
// module format: 'module:name=<module-name>,ver=21005,api=1,filters=0,usedby=[],using=[],options=[]'
module := strings.Split(split[1], ",")
if len(module) != 7 {
continue
}
e.registerConstMetricGauge(ch, "module_info", 1,
strings.Split(module[0], "=")[1],
strings.Split(module[1], "=")[1],
strings.Split(module[2], "=")[1],
strings.Split(module[3], "=")[1],
strings.Split(module[4], "=")[1],
strings.Split(module[5], "=")[1],
)
continue
}

fieldKey := split[0]
fieldValue := split[1]

if !e.includeMetric(fieldKey) {
continue
}
e.parseAndRegisterConstMetric(ch, fieldKey, fieldValue)
}
}
74 changes: 74 additions & 0 deletions exporter/modules_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package exporter

import (
"os"
"strings"
"testing"

"github.com/prometheus/client_golang/prometheus"
)

func TestModules(t *testing.T) {
if os.Getenv("TEST_REDIS_MODULES_URI") == "" {
t.Skipf("TEST_REDIS_MODULES_URI not set - skipping")
}

tsts := []struct {
addr string
inclModulesMetrics bool
wantModulesMetrics bool
}{
{addr: os.Getenv("TEST_REDIS_MODULES_URI"), inclModulesMetrics: true, wantModulesMetrics: true},
{addr: os.Getenv("TEST_REDIS_MODULES_URI"), inclModulesMetrics: false, wantModulesMetrics: false},
{addr: os.Getenv("TEST_REDIS_URI"), inclModulesMetrics: true, wantModulesMetrics: false},
{addr: os.Getenv("TEST_REDIS_URI"), inclModulesMetrics: false, wantModulesMetrics: false},
}

for _, tst := range tsts {
e, _ := NewRedisExporter(tst.addr, Options{Namespace: "test", InclModulesMetrics: tst.inclModulesMetrics})

chM := make(chan prometheus.Metric)
go func() {
e.Collect(chM)
close(chM)
}()

wantedMetrics := map[string]bool{
"module_info": false,
"search_number_of_indexes": false,
"search_used_memory_indexes_bytes": false,
"search_total_indexing_time_ms": false,
"search_global_idle": false,
"search_global_total": false,
"search_collected_bytes": false,
"search_total_cycles": false,
"search_total_run_ms": false,
"search_dialect_1": false,
"search_dialect_2": false,
"search_dialect_3": false,
"search_dialect_4": false,
}

for m := range chM {
for want := range wantedMetrics {
if strings.Contains(m.Desc().String(), want) {
wantedMetrics[want] = true
}
}
}

if tst.wantModulesMetrics {
for want, found := range wantedMetrics {
if !found {
t.Errorf("%s was *not* found in Redis Modules metrics but expected", want)
}
}
} else if !tst.wantModulesMetrics {
for want, found := range wantedMetrics {
if found {
t.Errorf("%s was *found* in Redis Modules metrics but *not* expected", want)
}
}
}
}
}
2 changes: 2 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ func main() {
redisMetricsOnly = flag.Bool("redis-only-metrics", getEnvBool("REDIS_EXPORTER_REDIS_ONLY_METRICS", false), "Whether to also export 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")
disableExportingKeyValues = flag.Bool("disable-exporting-key-values", getEnvBool("REDIS_EXPORTER_DISABLE_EXPORTING_KEY_VALUES", false), "Whether to disable values of keys stored in redis as labels or not when using check-keys/check-single-key")
excludeLatencyHistogramMetrics = flag.Bool("exclude-latency-histogram-metrics", getEnvBool("REDIS_EXPORTER_EXCLUDE_LATENCY_HISTOGRAM_METRICS", false), "Do not try to collect latency histogram metrics")
redactConfigMetrics = flag.Bool("redact-config-metrics", getEnvBool("REDIS_EXPORTER_REDACT_CONFIG_METRICS", true), "Whether to redact config settings that include potentially sensitive information like passwords")
Expand Down Expand Up @@ -182,6 +183,7 @@ func main() {
SetClientName: *setClientName,
IsTile38: *isTile38,
IsCluster: *isCluster,
InclModulesMetrics: *inclModulesMetrics,
ExportClientList: *exportClientList,
ExportClientsInclPort: *exportClientPort,
SkipTLSVerification: *skipTLSVerification,
Expand Down

0 comments on commit 1f06f2f

Please sign in to comment.