Skip to content

Commit

Permalink
Merge pull request #57 from StephaneBunel/master
Browse files Browse the repository at this point in the history
Add flag -redis.alias. Allow to give an alias to the redis node(s) address
  • Loading branch information
oliver006 authored Mar 8, 2017
2 parents 9575c71 + 86abe8a commit 70ba92f
Show file tree
Hide file tree
Showing 5 changed files with 1,140 additions and 35 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ log-format | Log format, valid options are `txt` (default) and `json`.
check-keys | Comma separated list of keys to export value and length/size, eg: `db3=user_count` will export key `user_count` from db `3`. db defaults to `0` if omitted.
redis.addr | Address of one or more redis nodes, comma separated, defaults to `redis://localhost:6379`.
redis.password | Password to use when authenticating to Redis
redis.alias | Alias for redis node addr, comma separated.
namespace | Namespace for the metrics, defaults to `redis`.
web.listen-address | Address to listen on for web interface and telemetry, defaults to `0.0.0.0:9121`.
web.telemetry-path | Path under which to expose metrics, defaults to `metrics`.
Expand All @@ -62,6 +63,7 @@ Name | Description
-------------------|------------
REDIS_ADDR | Address of Redis node(s)
REDIS_PASSWORD | Password to use when authenticating to Redis
REDIS_ALIAS | Alias name of Redis node(s)

### What's exported?

Expand All @@ -78,7 +80,7 @@ Example [Grafana](http://grafana.org/) screenshots:<br>

Grafana dashboard is available on [grafana.net](https://grafana.net/dashboards/763) and/or [github.com](https://github.com/oliver006/redis_exporter/blob/master/grafana_prometheus_redis_dashboard.json).


Grafana dashboard with host & alias selector is available on [github.com](https://github.com/oliver006/redis_exporter/blob/master/grafana_prometheus_redis_dashboard_alias.json).

### What else?

Expand Down
53 changes: 28 additions & 25 deletions exporter/redis.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
type RedisHost struct {
Addrs []string
Passwords []string
Aliases []string
}

type dbKeyPair struct {
Expand All @@ -42,6 +43,7 @@ type scrapeResult struct {
Name string
Value float64
Addr string
Alias string
DB string
}

Expand Down Expand Up @@ -109,29 +111,29 @@ func (e *Exporter) initGauges() {
Namespace: e.namespace,
Name: "db_keys",
Help: "Total number of keys by DB",
}, []string{"addr", "db"})
}, []string{"addr", "alias", "db"})
e.metrics["db_keys_expiring"] = prometheus.NewGaugeVec(prometheus.GaugeOpts{
Namespace: e.namespace,
Name: "db_keys_expiring",
Help: "Total number of expiring keys by DB",
}, []string{"addr", "db"})
}, []string{"addr", "alias", "db"})
e.metrics["db_avg_ttl_seconds"] = prometheus.NewGaugeVec(prometheus.GaugeOpts{
Namespace: e.namespace,
Name: "db_avg_ttl_seconds",
Help: "Avg TTL in seconds",
}, []string{"addr", "db"})
}, []string{"addr", "alias", "db"})

// Emulate a Summary.
e.metrics["command_call_duration_seconds_count"] = prometheus.NewGaugeVec(prometheus.GaugeOpts{
Namespace: e.namespace,
Name: "command_call_duration_seconds_count",
Help: "Total number of calls per command",
}, []string{"addr", "cmd"})
}, []string{"addr", "alias", "cmd"})
e.metrics["command_call_duration_seconds_sum"] = prometheus.NewGaugeVec(prometheus.GaugeOpts{
Namespace: e.namespace,
Name: "command_call_duration_seconds_sum",
Help: "Total amount of time in seconds spent per command",
}, []string{"addr", "cmd"})
}, []string{"addr", "alias", "cmd"})
}

// NewRedisExporter returns a new exporter of Redis metrics.
Expand All @@ -145,12 +147,12 @@ func NewRedisExporter(host RedisHost, namespace, checkKeys string) (*Exporter, e
Namespace: namespace,
Name: "key_value",
Help: "The value of \"key\"",
}, []string{"addr", "db", "key"}),
}, []string{"addr", "alias", "db", "key"}),
keySizes: prometheus.NewGaugeVec(prometheus.GaugeOpts{
Namespace: namespace,
Name: "key_size",
Help: "The length or size of \"key\"",
}, []string{"addr", "db", "key"}),
}, []string{"addr", "alias", "db", "key"}),
duration: prometheus.NewGauge(prometheus.GaugeOpts{
Namespace: namespace,
Name: "exporter_last_scrape_duration_seconds",
Expand Down Expand Up @@ -288,7 +290,7 @@ func parseDBKeyspaceString(db string, stats string) (keysTotal float64, keysExpi
return
}

func (e *Exporter) extractInfoMetrics(info, addr string, scrapes chan<- scrapeResult) error {
func (e *Exporter) extractInfoMetrics(info, addr string, alias string, scrapes chan<- scrapeResult) error {
cmdstats := false
lines := strings.Split(info, "\r\n")
for _, line := range lines {
Expand Down Expand Up @@ -339,17 +341,17 @@ func (e *Exporter) extractInfoMetrics(info, addr string, scrapes chan<- scrapeRe
}

e.metricsMtx.RLock()
e.metrics["command_call_duration_seconds_count"].WithLabelValues(addr, cmd).Set(calls)
e.metrics["command_call_duration_seconds_sum"].WithLabelValues(addr, cmd).Set(usecTotal / 1e6)
e.metrics["command_call_duration_seconds_count"].WithLabelValues(addr, alias, cmd).Set(calls)
e.metrics["command_call_duration_seconds_sum"].WithLabelValues(addr, alias, cmd).Set(usecTotal / 1e6)
e.metricsMtx.RUnlock()
continue
}

if keysTotal, keysEx, avgTTL, ok := parseDBKeyspaceString(split[0], split[1]); ok {
scrapes <- scrapeResult{Name: "db_keys", Addr: addr, DB: split[0], Value: keysTotal}
scrapes <- scrapeResult{Name: "db_keys_expiring", Addr: addr, DB: split[0], Value: keysEx}
scrapes <- scrapeResult{Name: "db_keys", Addr: addr, Alias: alias, DB: split[0], Value: keysTotal}
scrapes <- scrapeResult{Name: "db_keys_expiring", Addr: addr, Alias: alias, DB: split[0], Value: keysEx}
if avgTTL > -1 {
scrapes <- scrapeResult{Name: "db_avg_ttl_seconds", Addr: addr, DB: split[0], Value: avgTTL}
scrapes <- scrapeResult{Name: "db_avg_ttl_seconds", Addr: addr, Alias: alias, DB: split[0], Value: avgTTL}
}
continue
}
Expand Down Expand Up @@ -379,12 +381,12 @@ func (e *Exporter) extractInfoMetrics(info, addr string, scrapes chan<- scrapeRe
continue
}

scrapes <- scrapeResult{Name: metricName, Addr: addr, Value: val}
scrapes <- scrapeResult{Name: metricName, Addr: addr, Alias: alias, Value: val}
}
return nil
}

func extractConfigMetrics(config []string, addr string, scrapes chan<- scrapeResult) error {
func extractConfigMetrics(config []string, addr string, alias string, scrapes chan<- scrapeResult) error {

if len(config)%2 != 0 {
return fmt.Errorf("invalid config: %#v", config)
Expand All @@ -396,7 +398,7 @@ func extractConfigMetrics(config []string, addr string, scrapes chan<- scrapeRes
log.Debugf("couldn't parse %s, err: %s", config[pos*2+1], err)
continue
}
scrapes <- scrapeResult{Name: fmt.Sprintf("config_%s", config[pos*2]), Addr: addr, Value: val}
scrapes <- scrapeResult{Name: fmt.Sprintf("config_%s", config[pos*2]), Addr: addr, Alias: alias, Value: val}
}
return nil
}
Expand All @@ -411,8 +413,9 @@ func (e *Exporter) scrape(scrapes chan<- scrapeResult) {
for idx, addr := range e.redis.Addrs {
var c redis.Conn
var err error
alias := e.redis.Aliases[idx]

scrapes <- scrapeResult{Name: "up", Addr: addr, Value: 0}
scrapes <- scrapeResult{Name: "up", Addr: addr, Alias: alias, Value: 0}

var options []redis.DialOption
if len(e.redis.Passwords) > idx && e.redis.Passwords[idx] != "" {
Expand Down Expand Up @@ -442,7 +445,7 @@ func (e *Exporter) scrape(scrapes chan<- scrapeResult) {

info, err := redis.String(c.Do("INFO", "ALL"))
if err == nil {
err = e.extractInfoMetrics(info, addr, scrapes)
err = e.extractInfoMetrics(info, addr, alias, scrapes)
} else {
log.Printf("redis err: %s", err)
errorCount++
Expand All @@ -452,18 +455,18 @@ func (e *Exporter) scrape(scrapes chan<- scrapeResult) {
if strings.Index(info, "cluster_enabled:1") != -1 {
info, err = redis.String(c.Do("CLUSTER", "INFO"))
if err == nil {
err = e.extractInfoMetrics(info, addr, scrapes)
err = e.extractInfoMetrics(info, addr, alias, scrapes)
} else {
log.Printf("redis err: %s", err)
errorCount++
continue
}
}

scrapes <- scrapeResult{Name: "up", Addr: addr, Value: 1}
scrapes <- scrapeResult{Name: "up", Addr: addr, Alias: alias, Value: 1}

if config, err := redis.Strings(c.Do("CONFIG", "GET", "maxmemory")); err == nil {
extractConfigMetrics(config, addr, scrapes)
extractConfigMetrics(config, addr, alias, scrapes)
}

for _, k := range e.keys {
Expand All @@ -472,7 +475,7 @@ func (e *Exporter) scrape(scrapes chan<- scrapeResult) {
}
if tempVal, err := c.Do("GET", k.key); err == nil && tempVal != nil {
if val, err := strconv.ParseFloat(fmt.Sprintf("%s", tempVal), 64); err == nil {
e.keyValues.WithLabelValues(addr, "db"+k.db, k.key).Set(val)
e.keyValues.WithLabelValues(addr, alias, "db"+k.db, k.key).Set(val)
}
}

Expand All @@ -485,7 +488,7 @@ func (e *Exporter) scrape(scrapes chan<- scrapeResult) {
"STRLEN",
} {
if tempVal, err := c.Do(op, k.key); err == nil && tempVal != nil {
e.keySizes.WithLabelValues(addr, "db"+k.db, k.key).Set(float64(tempVal.(int64)))
e.keySizes.WithLabelValues(addr, alias, "db"+k.db, k.key).Set(float64(tempVal.(int64)))
break
}
}
Expand All @@ -504,10 +507,10 @@ func (e *Exporter) setMetrics(scrapes <-chan scrapeResult) {
e.metrics[name] = prometheus.NewGaugeVec(prometheus.GaugeOpts{
Namespace: e.namespace,
Name: name,
}, []string{"addr"})
}, []string{"addr", "alias"})
e.metricsMtx.Unlock()
}
var labels prometheus.Labels = map[string]string{"addr": scr.Addr}
var labels prometheus.Labels = map[string]string{"addr": scr.Addr, "alias": scr.Alias}
if len(scr.DB) > 0 {
labels["db"] = scr.DB
}
Expand Down
21 changes: 14 additions & 7 deletions exporter/redis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ import (
const TestValue = 1234.56

var (
redisAddr = flag.String("redis.addr", "localhost:6379", "Address of the test instance, without `redis://`")
redisAddr = flag.String("redis.addr", "localhost:6379", "Address of the test instance, without `redis://`")
redisAlias = flag.String("redis.alias", "foo", "Alias of the test instance")
separator = flag.String("separator", ",", "separator used to split redis.addr, redis.password and redis.alias into several elements.")

keys = []string{}
keysExpiring = []string{}
Expand Down Expand Up @@ -114,7 +116,7 @@ func deleteKeysFromDB(t *testing.T, addr string) error {
func TestHostVariations(t *testing.T) {
for _, prefix := range []string{"", "redis://", "tcp://"} {
addr := prefix + *redisAddr
host := RedisHost{Addrs: []string{addr}}
host := RedisHost{Addrs: []string{addr}, Aliases: []string{""}}
e, _ := NewRedisExporter(host, "test", "")

scrapes := make(chan scrapeResult, 10000)
Expand Down Expand Up @@ -381,7 +383,7 @@ func TestHTTPEndpoint(t *testing.T) {

func TestNonExistingHost(t *testing.T) {

rr := RedisHost{Addrs: []string{"unix:///tmp/doesnt.exist"}}
rr := RedisHost{Addrs: []string{"unix:///tmp/doesnt.exist"}, Aliases: []string{""}}
e, _ := NewRedisExporter(rr, "test", "")

chM := make(chan prometheus.Metric)
Expand Down Expand Up @@ -475,7 +477,7 @@ func TestMoreThanOneHost(t *testing.T) {
return
}

twoHostCfg := RedisHost{Addrs: []string{firstHost, secondHost}}
twoHostCfg := RedisHost{Addrs: []string{firstHost, secondHost}, Aliases: []string{"", ""}}
checkKey := dbNumStrFull + "=" + url.QueryEscape(keys[0])
e, _ := NewRedisExporter(twoHostCfg, "test", checkKey)

Expand Down Expand Up @@ -532,11 +534,16 @@ func init() {
}

flag.Parse()
addrs := strings.Split(*redisAddr, ",")
addrs := strings.Split(*redisAddr, *separator)
if len(addrs) == 0 || len(addrs[0]) == 0 {
log.Fatal("Invalid parameter --redis.addr")
}
log.Printf("Using redis addrs: %#v", addrs)

defaultRedisHost = RedisHost{Addrs: []string{"redis://" + *redisAddr}}
aliases := strings.Split(*redisAlias, *separator)
for len(aliases) < len(addrs) {
aliases = append(aliases, aliases[0])
}

log.Printf("Using redis addrs: %#v", addrs)
defaultRedisHost = RedisHost{Addrs: []string{"redis://" + *redisAddr}, Aliases: aliases}
}
Loading

0 comments on commit 70ba92f

Please sign in to comment.