Skip to content

Commit

Permalink
Merge pull request #1 from ResultadosDigitais/multiple-uris
Browse files Browse the repository at this point in the history
add -redis.file option back to support multiple redis instances
  • Loading branch information
raphapr authored Jul 14, 2020
2 parents 3e15a27 + 22819ff commit 95e1efd
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 60 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ Prometheus uses file watches and all changes to the json file are applied immedi
Name | Environment Variable Name | Description
-----------------------|--------------------------------------|-----------------
redis.addr | REDIS_ADDR | Address of the Redis instance, defaults to `redis://localhost:6379`.
redis.file | REDIS_FILE | Path to file containing one or more redis nodes, separated by newline. Format: `<redis URI>,<optional password>,<optional alias>` NOTE: mutually exclusive with redis.addr
redis.user | REDIS_USER | User name to use for authentication (Redis ACL for Redis 6.0 and newer).
redis.password | REDIS_PASSWORD | Password of the Redis instance, defaults to `""` (no password).
check-keys | REDIS_EXPORTER_CHECK_KEYS | Comma separated list of key patterns to export value and length/size, eg: `db3=user_count` will export key `user_count` from db `3`. db defaults to `0` if omitted. The key patterns specified with this flag will be found using [SCAN](https://redis.io/commands/scan). Use this option if you need glob pattern matching; `check-single-keys` is faster for non-pattern keys. Warning: using `--check-keys` to match a very large number of keys can slow down the exporter to the point where it doesn't finish scraping the redis instance.
Expand Down
132 changes: 98 additions & 34 deletions exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ package main
import (
"crypto/tls"
"crypto/x509"
"encoding/csv"
"errors"
"fmt"
"net/http"
"net/url"
"os"
"regexp"
"runtime"
"strconv"
Expand All @@ -20,6 +22,13 @@ import (
log "github.com/sirupsen/logrus"
)

type serverInfo struct {
addr string
password string
alias string
addConstLabels bool
}

type dbKeyPair struct {
db, key string
}
Expand All @@ -33,24 +42,25 @@ type keyInfo struct {
type Exporter struct {
sync.Mutex

redisAddr string
namespace string
server serverInfo
namespace string
constLabels prometheus.Labels

totalScrapes prometheus.Counter
scrapeDuration prometheus.Summary
targetScrapeRequestErrors prometheus.Counter

metricDescriptions map[string]*prometheus.Desc

options Options
options ExporterOptions

metricMapCounters map[string]string
metricMapGauges map[string]string

mux *http.ServeMux
}

type Options struct {
type ExporterOptions struct {
User string
Password string
Namespace string
Expand Down Expand Up @@ -148,35 +158,55 @@ func parseKeyArg(keysArgString string) (keys []dbKeyPair, err error) {
return keys, err
}

func newMetricDescr(namespace string, metricName string, docString string, labels []string) *prometheus.Desc {
return prometheus.NewDesc(prometheus.BuildFQName(namespace, "", metricName), docString, labels, nil)
func newMetricDescr(namespace string, metricName string, docString string, labels []string, constLabels prometheus.Labels) *prometheus.Desc {
return prometheus.NewDesc(prometheus.BuildFQName(namespace, "", metricName), docString, labels, constLabels)
}

// NewRedisExporter returns a new exporter of Redis metrics.
func NewRedisExporter(redisURI string, opts Options) (*Exporter, error) {
func NewRedisExporter(serverArg interface{}, opts ExporterOptions) (*Exporter, error) {
log.Debugf("NewRedisExporter options: %#v", opts)

var server serverInfo
switch v := serverArg.(type) {
case serverInfo:
server = v
case string:
server.addr = v
}

constLabels := prometheus.Labels{}
if server.addConstLabels {
if server.alias == "" {
server.alias = server.addr
}
constLabels = prometheus.Labels{"addr": server.addr, "alias": server.alias}
}

e := &Exporter{
redisAddr: redisURI,
options: opts,
namespace: opts.Namespace,
server: server,
constLabels: constLabels,
options: opts,
namespace: opts.Namespace,

totalScrapes: prometheus.NewCounter(prometheus.CounterOpts{
Namespace: opts.Namespace,
Name: "exporter_scrapes_total",
Help: "Current total redis scrapes.",
Namespace: opts.Namespace,
Name: "exporter_scrapes_total",
Help: "Current total redis scrapes.",
ConstLabels: constLabels,
}),

scrapeDuration: prometheus.NewSummary(prometheus.SummaryOpts{
Namespace: opts.Namespace,
Name: "exporter_scrape_duration_seconds",
Help: "Durations of scrapes by the exporter",
Namespace: opts.Namespace,
Name: "exporter_scrape_duration_seconds",
Help: "Durations of scrapes by the exporter",
ConstLabels: constLabels,
}),

targetScrapeRequestErrors: prometheus.NewCounter(prometheus.CounterOpts{
Namespace: opts.Namespace,
Name: "target_scrape_request_errors_total",
Help: "Errors in requests to the exporter",
Namespace: opts.Namespace,
Name: "target_scrape_request_errors_total",
Help: "Errors in requests to the exporter",
ConstLabels: constLabels,
}),

metricMapGauges: map[string]string{
Expand Down Expand Up @@ -385,7 +415,7 @@ func NewRedisExporter(redisURI string, opts Options) (*Exporter, error) {
"up": {txt: "Information about the Redis instance"},
"connected_clients_details": {txt: "Details about connected clients", lbls: []string{"host", "port", "name", "age", "idle", "flags", "db", "cmd"}},
} {
e.metricDescriptions[k] = newMetricDescr(opts.Namespace, k, desc.txt, desc.lbls)
e.metricDescriptions[k] = newMetricDescr(opts.Namespace, k, desc.txt, desc.lbls, constLabels)
}

if e.options.MetricsPath == "" {
Expand All @@ -402,9 +432,10 @@ func NewRedisExporter(redisURI string, opts Options) (*Exporter, error) {

if !e.options.RedisMetricsOnly {
buildInfo := prometheus.NewGaugeVec(prometheus.GaugeOpts{
Namespace: opts.Namespace,
Name: "exporter_build_info",
Help: "redis exporter build_info",
Namespace: opts.Namespace,
Name: "exporter_build_info",
Help: "redis exporter build_info",
ConstLabels: constLabels,
}, []string{"version", "commit_sha", "build_date", "golang_version"})
buildInfo.WithLabelValues(BuildVersion, BuildCommitSha, BuildDate, runtime.Version()).Set(1)
e.options.Registry.MustRegister(buildInfo)
Expand Down Expand Up @@ -440,11 +471,11 @@ func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {
}

for _, v := range e.metricMapGauges {
ch <- newMetricDescr(e.options.Namespace, v, v+" metric", nil)
ch <- newMetricDescr(e.options.Namespace, v, v+" metric", nil, e.constLabels)
}

for _, v := range e.metricMapCounters {
ch <- newMetricDescr(e.options.Namespace, v, v+" metric", nil)
ch <- newMetricDescr(e.options.Namespace, v, v+" metric", nil, e.constLabels)
}

ch <- e.totalScrapes.Desc()
Expand All @@ -458,7 +489,7 @@ func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
defer e.Unlock()
e.totalScrapes.Inc()

if e.redisAddr != "" {
if e.server.addr != "" {
startTime := time.Now()
var up float64 = 1
if err := e.scrapeRedisHost(ch); err != nil {
Expand Down Expand Up @@ -670,7 +701,7 @@ func (e *Exporter) registerConstMetricGauge(ch chan<- prometheus.Metric, metric
func (e *Exporter) registerConstMetric(ch chan<- prometheus.Metric, metric string, val float64, valType prometheus.ValueType, labelValues ...string) {
descr := e.metricDescriptions[metric]
if descr == nil {
descr = newMetricDescr(e.options.Namespace, metric, metric+" metric", labelValues)
descr = newMetricDescr(e.options.Namespace, metric, metric+" metric", labelValues, e.constLabels)
}

if m, err := prometheus.NewConstMetric(descr, valType, val, labelValues...); err == nil {
Expand Down Expand Up @@ -1197,11 +1228,13 @@ func (e *Exporter) connectToRedis() (redis.Conn, error) {
options = append(options, redis.DialUsername(e.options.User))
}

if e.options.Password != "" {
if e.server.password != "" {
options = append(options, redis.DialPassword(e.server.password))
} else if e.options.Password != "" {
options = append(options, redis.DialPassword(e.options.Password))
}

uri := e.redisAddr
uri := e.server.addr
if !strings.Contains(uri, "://") {
uri = "redis://" + uri
}
Expand All @@ -1210,12 +1243,12 @@ func (e *Exporter) connectToRedis() (redis.Conn, error) {
c, err := redis.DialURL(uri, options...)
if err != nil {
log.Debugf("DialURL() failed, err: %s", err)
if frags := strings.Split(e.redisAddr, "://"); len(frags) == 2 {
if frags := strings.Split(e.server.addr, "://"); len(frags) == 2 {
log.Debugf("Trying: Dial(): %s %s", frags[0], frags[1])
c, err = redis.Dial(frags[0], frags[1], options...)
} else {
log.Debugf("Trying: Dial(): tcp %s", e.redisAddr)
c, err = redis.Dial("tcp", e.redisAddr, options...)
log.Debugf("Trying: Dial(): tcp %s", e.server.addr)
c, err = redis.Dial("tcp", e.server.addr, options...)
}
}
return c, err
Expand All @@ -1231,12 +1264,12 @@ func (e *Exporter) scrapeRedisHost(ch chan<- prometheus.Metric) error {

if err != nil {
log.Errorf("Couldn't connect to redis instance")
log.Debugf("connectToRedis( %s ) err: %s", e.redisAddr, err)
log.Debugf("connectToRedis( %s ) err: %s", e.server.addr, err)
return err
}
defer c.Close()

log.Debugf("connected to: %s", e.redisAddr)
log.Debugf("connected to: %s", e.server.addr)
log.Debugf("connecting took %f seconds", connectTookSeconds)

if e.options.PingOnConnect {
Expand Down Expand Up @@ -1322,3 +1355,34 @@ func (e *Exporter) scrapeRedisHost(ch chan<- prometheus.Metric) error {

return nil
}

// loadRedisFile opens the specified file and loads the configuration for which redis
// hosts to monitor. Returns the list of hosts addrs, passwords, and their aliases.
func loadRedisFile(fileName string) ([]serverInfo, error) {
var servers []serverInfo
file, err := os.Open(fileName)
if err != nil {
return nil, err
}
r := csv.NewReader(file)
r.FieldsPerRecord = -1
records, err := r.ReadAll()
if err != nil {
return nil, err
}
file.Close()
// For each line, test if it contains an optional password and alias and provide them,
// else give them empty strings
for _, record := range records {
length := len(record)
switch length {
case 3:
servers = append(servers, serverInfo{addr: record[0], password: record[1], alias: record[2]})
case 2:
servers = append(servers, serverInfo{addr: record[0], password: record[1]})
case 1:
servers = append(servers, serverInfo{addr: record[0]})
}
}
return servers, nil
}
5 changes: 5 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k=
github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
Expand Down Expand Up @@ -77,11 +78,13 @@ github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6T
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.9.1 h1:KOMtN28tlbam3/7ZKEYKHhKoJZYYj3gMH4uc62x7X7U=
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.11 h1:DhHlBtkHWPYi8O2y31JkK0TF+DGM+51OopZjH/Ia5qI=
github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
Expand Down Expand Up @@ -111,6 +114,7 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f h1:gWF768j/LaZugp8dyS4UwsslYCYz9XgFxvlgsn0n9H8=
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
Expand All @@ -121,6 +125,7 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
68 changes: 42 additions & 26 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ func main() {
var (
redisAddr = flag.String("redis.addr", getEnv("REDIS_ADDR", "redis://localhost:6379"), "Address of the Redis instance to scrape")
redisUser = flag.String("redis.user", getEnv("REDIS_USER", ""), "User name to use for authentication (Redis ACL for Redis 6.0 and newer)")
redisFile = flag.String("redis.file", getEnv("REDIS_FILE", ""), "Path to file containing one or more redis nodes, separated by newline. NOTE: mutually exclusive with redis.addr")
redisPwd = flag.String("redis.password", getEnv("REDIS_PASSWORD", ""), "Password of the Redis instance to scrape")
namespace = flag.String("namespace", getEnv("REDIS_EXPORTER_NAMESPACE", "redis"), "Namespace for metrics")
checkKeys = flag.String("check-keys", getEnv("REDIS_EXPORTER_CHECK_KEYS", ""), "Comma separated list of key-patterns to export value and length/size, searched for with SCAN")
Expand Down Expand Up @@ -134,32 +135,47 @@ func main() {
registry = prometheus.DefaultRegisterer.(*prometheus.Registry)
}

exp, err := NewRedisExporter(
*redisAddr,
Options{
User: *redisUser,
Password: *redisPwd,
Namespace: *namespace,
ConfigCommandName: *configCommand,
CheckKeys: *checkKeys,
CheckSingleKeys: *checkSingleKeys,
LuaScript: ls,
InclSystemMetrics: *inclSystemMetrics,
SetClientName: *setClientName,
IsTile38: *isTile38,
ExportClientList: *exportClientList,
SkipTLSVerification: *skipTLSVerification,
ClientCertificates: tlsClientCertificates,
CaCertificates: tlsCaCertificates,
ConnectionTimeouts: to,
MetricsPath: *metricPath,
RedisMetricsOnly: *redisMetricsOnly,
PingOnConnect: *pingOnConnect,
Registry: registry,
},
)
if err != nil {
log.Fatal(err)
servers := []serverInfo{serverInfo{addr: *redisAddr, password: *redisPwd, alias: *redisAddr}}
if *redisFile != "" {
if servers, err = loadRedisFile(*redisFile); err != nil {
log.Fatal(err)
}
}

var exp *Exporter
for _, server := range servers {
if len(servers) > 1 {
server.addConstLabels = true
}
exp, err = NewRedisExporter(
server,
ExporterOptions{
User: *redisUser,
Password: *redisPwd,
Namespace: *namespace,
ConfigCommandName: *configCommand,
CheckKeys: *checkKeys,
CheckSingleKeys: *checkSingleKeys,
LuaScript: ls,
InclSystemMetrics: *inclSystemMetrics,
SetClientName: *setClientName,
IsTile38: *isTile38,
ExportClientList: *exportClientList,
SkipTLSVerification: *skipTLSVerification,
ClientCertificates: tlsClientCertificates,
CaCertificates: tlsCaCertificates,
ConnectionTimeouts: to,
MetricsPath: *metricPath,
RedisMetricsOnly: *redisMetricsOnly,
PingOnConnect: *pingOnConnect,
Registry: registry,
},
)
if err != nil {
log.Fatal(err)
} else {
log.Infof("Configured with redis addr: %s [%s]", server.addr, server.alias)
}
}

log.Infof("Providing metrics at %s%s", *listenAddress, *metricPath)
Expand Down

0 comments on commit 95e1efd

Please sign in to comment.