diff --git a/.drone.yml b/.drone.yml index feb7d3fb..9e2a20ea 100644 --- a/.drone.yml +++ b/.drone.yml @@ -59,7 +59,7 @@ steps: - 'echo "checking gofmt"' - 'echo " ! gofmt -d *.go 2>&1 | read " | bash' - 'echo "checking gofmt - DONE"' - - 'go vet .' + - 'go vet -mod=vendor .' - 'go get github.com/mattn/goveralls' - '/go/bin/goveralls -coverprofile=coverage.txt -service=drone.io' when: diff --git a/README.md b/README.md index 8de29ed8..78feef4a 100644 --- a/README.md +++ b/README.md @@ -75,12 +75,12 @@ The Redis instances are listed under `targets`, the Redis exporter hostname is c If authentication is needed for the Redis instances then you can set the password via the `--redis.password` command line option of the exporter (this means you can currently only use one password across the instances you try to scrape this way. Use several exporters if this is a problem). \ -You can also use a file supply multiple targets by using `file_sd_configs` like so: +You can also use a json file to supply multiple targets by using `file_sd_configs` like so: ```yaml scrape_configs: - - job_name: 'redis_exporter' + - job_name: 'redis_exporter_targets' file_sd_configs: - files: - targets-redis-instances.json @@ -92,6 +92,12 @@ scrape_configs: target_label: instance - target_label: __address__ replacement: <>:9121 + + ## config for scraping the exporter itself + - job_name: 'redis_exporter' + static_configs: + - targets: + - <>:9121 ``` The `targets-redis-instances.json` should look something like this: @@ -186,20 +192,3 @@ Grafana dashboard is available on [grafana.net](https://grafana.net/dashboards/7 ## What else? Open an issue or PR if you have more suggestions, questions or ideas about what to add. - -## Troubleshooting / Known issues - -[#282 **check-keys** parameter is not working](https://github.com/oliver006/redis_exporter/issues/282) in latest build. You will need to add parameter to Prometheus like - -``` -scrape_configs: - ## config for the multiple Redis targets that the exporter will scrape - - job_name: 'redis_exporter_targets' - static_configs: - - targets: - - redis://first-redis-host:6379 - - redis://second-redis-host:6379 - metrics_path: /scrape - params: - check-keys: ["metrics:*"] -``` diff --git a/build-github-binaries.sh b/build-github-binaries.sh index 3292f7a4..4ac35e7d 100755 --- a/build-github-binaries.sh +++ b/build-github-binaries.sh @@ -21,6 +21,8 @@ if [[ -f 'go.mod' ]] ; then fi gox -verbose -os="darwin linux windows freebsd netbsd openbsd" -arch="386 amd64" -rebuild -ldflags "${GO_LDFLAGS}" -output ".build/redis_exporter-${DRONE_TAG}.{{.OS}}-{{.Arch}}/{{.Dir}}" +gox -verbose -os="linux freebsd netbsd" -arch="arm" -rebuild -ldflags "${GO_LDFLAGS}" -output ".build/redis_exporter-${DRONE_TAG}.{{.OS}}-{{.Arch}}/{{.Dir}}" +gox -verbose -os="linux" -arch="arm64 mips64 mips64le ppc64 ppc64le s390x" -rebuild -ldflags "${GO_LDFLAGS}" -output ".build/redis_exporter-${DRONE_TAG}.{{.OS}}-{{.Arch}}/{{.Dir}}" mkdir -p dist for build in $(ls .build); do diff --git a/exporter.go b/exporter.go index d5a463bc..0578cfaa 100644 --- a/exporter.go +++ b/exporter.go @@ -7,6 +7,7 @@ import ( "net/http" "net/url" "regexp" + "runtime" "strconv" "strings" "sync" @@ -31,10 +32,8 @@ type keyInfo struct { // Exporter implements the prometheus.Exporter interface, and exports Redis metrics. type Exporter struct { sync.Mutex - redisAddr string - namespace string - keys []dbKeyPair - singleKeys []dbKeyPair + redisAddr string + namespace string totalScrapes prometheus.Counter scrapeDuration prometheus.Summary @@ -42,11 +41,12 @@ type Exporter struct { metricDescriptions map[string]*prometheus.Desc - options ExporterOptions - LuaScript []byte + options ExporterOptions metricMapCounters map[string]string metricMapGauges map[string]string + + mux *http.ServeMux } type ExporterOptions struct { @@ -55,12 +55,16 @@ type ExporterOptions struct { ConfigCommandName string CheckSingleKeys string CheckKeys string + LuaScript []byte ClientCertificates []tls.Certificate InclSystemMetrics bool SkipTLSVerification bool IsTile38 bool ExportClientList bool ConnectionTimeouts time.Duration + MetricsPath string + RedisMetricsOnly bool + Registry *prometheus.Registry } func (e *Exporter) ScrapeHandler(w http.ResponseWriter, r *http.Request) { @@ -73,7 +77,7 @@ func (e *Exporter) ScrapeHandler(w http.ResponseWriter, r *http.Request) { u, err := url.Parse(target) if err != nil { - http.Error(w, fmt.Sprintf("Invalid 'target' parameter, parse err: %s ", err), 400) + http.Error(w, fmt.Sprintf("Invalid 'target' parameter, parse err: %ck ", err), 400) e.targetScrapeRequestErrors.Inc() return } @@ -82,21 +86,25 @@ func (e *Exporter) ScrapeHandler(w http.ResponseWriter, r *http.Request) { u.User = nil target = u.String() - checkKeys := r.URL.Query().Get("check-keys") - checkSingleKey := r.URL.Query().Get("check-single-keys") - opts := e.options - opts.CheckKeys = checkKeys - opts.CheckSingleKeys = checkSingleKey - exp, err := NewRedisExporter(target, opts) + if ck := r.URL.Query().Get("check-keys"); ck != "" { + opts.CheckKeys = ck + } + + if csk := r.URL.Query().Get("check-single-keys"); csk != "" { + opts.CheckSingleKeys = csk + } + + registry := prometheus.NewRegistry() + opts.Registry = registry + + _, err = NewRedisExporter(target, opts) if err != nil { http.Error(w, "NewRedisExporter() err: err", 400) e.targetScrapeRequestErrors.Inc() return } - registry := prometheus.NewRegistry() - registry.MustRegister(exp) promhttp.HandlerFor(registry, promhttp.HandlerOpts{}).ServeHTTP(w, r) } @@ -135,7 +143,7 @@ func newMetricDescr(namespace string, metricName string, docString string, label // NewRedisExporter returns a new exporter of Redis metrics. func NewRedisExporter(redisURI string, opts ExporterOptions) (*Exporter, error) { - e := Exporter{ + e := &Exporter{ redisAddr: redisURI, options: opts, namespace: opts.Namespace, @@ -147,8 +155,9 @@ func NewRedisExporter(redisURI string, opts ExporterOptions) (*Exporter, error) }), scrapeDuration: prometheus.NewSummary(prometheus.SummaryOpts{ - Name: "exporter_scrape_duration_seconds", - Help: "Duration of scrape by the exporter", + Namespace: opts.Namespace, + Name: "exporter_scrape_duration_seconds", + Help: "Duration of scrape by the exporter", }), targetScrapeRequestErrors: prometheus.NewCounter(prometheus.CounterOpts{ @@ -262,17 +271,17 @@ func NewRedisExporter(redisURI string, opts ExporterOptions) (*Exporter, error) e.options.ConfigCommandName = "CONFIG" } - var err error - - if e.keys, err = parseKeyArg(opts.CheckKeys); err != nil { - return &e, fmt.Errorf("Couldn't parse check-keys: %#v", err) + if keys, err := parseKeyArg(opts.CheckKeys); err != nil { + return nil, fmt.Errorf("Couldn't parse check-keys: %#v", err) + } else { + log.Debugf("keys: %#v", keys) } - log.Debugf("keys: %#v", e.keys) - if e.singleKeys, err = parseKeyArg(opts.CheckSingleKeys); err != nil { - return &e, fmt.Errorf("Couldn't parse check-single-keys: %#v", err) + if singleKeys, err := parseKeyArg(opts.CheckSingleKeys); err != nil { + return nil, fmt.Errorf("Couldn't parse check-single-keys: %#v", err) + } else { + log.Debugf("singleKeys: %#v", singleKeys) } - log.Debugf("singleKeys: %#v", e.singleKeys) if opts.InclSystemMetrics { e.metricMapGauges["total_system_memory"] = "total_system_memory_bytes" @@ -310,7 +319,44 @@ func NewRedisExporter(redisURI string, opts ExporterOptions) (*Exporter, error) e.metricDescriptions[k] = newMetricDescr(opts.Namespace, k, desc.txt, desc.lbls) } - return &e, nil + if e.options.MetricsPath == "" { + e.options.MetricsPath = "/metrics" + } + + e.mux = http.NewServeMux() + + if e.options.Registry != nil { + e.options.Registry.MustRegister(e) + e.mux.Handle(e.options.MetricsPath, promhttp.HandlerFor(e.options.Registry, promhttp.HandlerOpts{})) + + if !e.options.RedisMetricsOnly { + buildInfo := prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: opts.Namespace, + Name: "exporter_build_info", + Help: "redis exporter build_info", + }, []string{"version", "commit_sha", "build_date", "golang_version"}) + buildInfo.WithLabelValues(BuildVersion, BuildCommitSha, BuildDate, runtime.Version()).Set(1) + e.options.Registry.MustRegister(buildInfo) + } + } + + e.mux.HandleFunc("/scrape", e.ScrapeHandler) + e.mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(` +Redis Exporter v` + BuildVersion + ` + +

Redis Exporter ` + BuildVersion + `

+

Metrics

+ + +`)) + }) + + return e, nil +} + +func (e *Exporter) ServeHTTP(w http.ResponseWriter, r *http.Request) { + e.mux.ServeHTTP(w, r) } // Describe outputs Redis metric descriptions. @@ -618,7 +664,7 @@ func (e *Exporter) handleMetricsServer(ch chan<- prometheus.Metric, fieldKey str } } -func (e *Exporter) extractInfoMetrics(ch chan<- prometheus.Metric, info string, dbCount int) error { +func (e *Exporter) extractInfoMetrics(ch chan<- prometheus.Metric, info string, dbCount int) { instanceInfo := map[string]string{} slaveInfo := map[string]string{} handledDBs := map[string]bool{} @@ -712,11 +758,9 @@ func (e *Exporter) extractInfoMetrics(ch chan<- prometheus.Metric, info string, slaveInfo["master_port"], slaveInfo["slave_read_only"]) } - - return nil } -func (e *Exporter) extractClusterInfoMetrics(ch chan<- prometheus.Metric, info string) error { +func (e *Exporter) extractClusterInfoMetrics(ch chan<- prometheus.Metric, info string) { lines := strings.Split(info, "\r\n") for _, line := range lines { @@ -735,16 +779,27 @@ func (e *Exporter) extractClusterInfoMetrics(ch chan<- prometheus.Metric, info s e.parseAndRegisterConstMetric(ch, fieldKey, fieldValue) } - - return nil } func (e *Exporter) extractCheckKeyMetrics(ch chan<- prometheus.Metric, c redis.Conn) { - log.Debugf("e.singleKeys: %#v", e.singleKeys) - allKeys := append([]dbKeyPair{}, e.singleKeys...) + keys, err := parseKeyArg(e.options.CheckKeys) + if err != nil { + log.Errorf("Couldn't parse check-keys: %#v", err) + return + } + log.Debugf("keys: %#v", keys) - log.Debugf("e.keys: %#v", e.keys) - scannedKeys, err := getKeysFromPatterns(c, e.keys) + singleKeys, err := parseKeyArg(e.options.CheckSingleKeys) + if err != nil { + log.Errorf("Couldn't parse check-single-keys: %#v", err) + return + } + log.Debugf("e.singleKeys: %#v", singleKeys) + + allKeys := append([]dbKeyPair{}, singleKeys...) + + log.Debugf("e.keys: %#v", keys) + scannedKeys, err := getKeysFromPatterns(c, keys) if err != nil { log.Errorf("Error expanding key patterns: %#v", err) } else { @@ -778,25 +833,24 @@ func (e *Exporter) extractCheckKeyMetrics(ch chan<- prometheus.Metric, c redis.C } } -func (e *Exporter) extractLuaScriptMetrics(ch chan<- prometheus.Metric, c redis.Conn) { - if e.LuaScript == nil || len(e.LuaScript) == 0 { - return - } - - log.Debug("Evaluating e.LuaScript") - kv, err := redis.StringMap(doRedisCmd(c, "EVAL", e.LuaScript, 0, 0)) +func (e *Exporter) extractLuaScriptMetrics(ch chan<- prometheus.Metric, c redis.Conn) error { + log.Debug("Evaluating e.options.LuaScript") + kv, err := redis.StringMap(doRedisCmd(c, "EVAL", e.options.LuaScript, 0, 0)) if err != nil { log.Errorf("LuaScript error: %v", err) - return + return err } - if kv != nil { - for key, stringVal := range kv { - if val, err := strconv.ParseFloat(stringVal, 64); err == nil { - e.registerConstMetricGauge(ch, "script_values", val, key) - } + if kv == nil || len(kv) == 0 { + return nil + } + + for key, stringVal := range kv { + if val, err := strconv.ParseFloat(stringVal, 64); err == nil { + e.registerConstMetricGauge(ch, "script_values", val, key) } } + return nil } func (e *Exporter) extractSlowLogMetrics(ch chan<- prometheus.Metric, c redis.Conn) { @@ -869,7 +923,7 @@ func (e *Exporter) extractConnectedClientMetrics(ch chan<- prometheus.Metric, c } } -func (e *Exporter) parseAndRegisterConstMetric(ch chan<- prometheus.Metric, fieldKey, fieldValue string) error { +func (e *Exporter) parseAndRegisterConstMetric(ch chan<- prometheus.Metric, fieldKey, fieldValue string) { orgMetricName := sanitizeMetricName(fieldKey) metricName := orgMetricName if newName, ok := e.metricMapGauges[metricName]; ok { @@ -911,8 +965,6 @@ func (e *Exporter) parseAndRegisterConstMetric(ch chan<- prometheus.Metric, fiel } e.registerConstMetric(ch, metricName, val, t) - - return nil } func doRedisCmd(c redis.Conn, cmd string, args ...interface{}) (reply interface{}, err error) { @@ -1110,10 +1162,14 @@ func (e *Exporter) scrapeRedisHost(ch chan<- prometheus.Metric) error { e.extractCheckKeyMetrics(ch, c) - e.extractLuaScriptMetrics(ch, c) - e.extractSlowLogMetrics(ch, c) + if e.options.LuaScript != nil && len(e.options.LuaScript) > 0 { + if err := e.extractLuaScriptMetrics(ch, c); err != nil { + return err + } + } + if e.options.ExportClientList { e.extractConnectedClientMetrics(ch, c) } diff --git a/exporter_test.go b/exporter_test.go index 71383067..3bc52f40 100644 --- a/exporter_test.go +++ b/exporter_test.go @@ -50,7 +50,7 @@ const ( ) func getTestExporter() *Exporter { - e, _ := NewRedisExporter(os.Getenv("TEST_REDIS_URI"), ExporterOptions{Namespace: "test"}) + e, _ := NewRedisExporter(os.Getenv("TEST_REDIS_URI"), ExporterOptions{Namespace: "test", Registry: prometheus.NewRegistry()}) return e } @@ -820,27 +820,71 @@ func TestKeySizeList(t *testing.T) { } } -func TestScript(t *testing.T) { +func TestLuaScript(t *testing.T) { e := getTestExporter() - e.LuaScript = []byte(`return {"a", "11", "b", "12", "c", "13"}`) - nKeys := 3 - setupDBKeys(t, os.Getenv("TEST_REDIS_URI")) - defer deleteKeysFromDB(t, os.Getenv("TEST_REDIS_URI")) + for _, tst := range []struct { + Script string + ExpectedKeys int + ExpectedError bool + }{ + { + Script: `return {"a", "11", "b", "12", "c", "13"}`, + ExpectedKeys: 3, + }, + { + Script: `return {"key1", "6389"}`, + ExpectedKeys: 1, + }, + { + Script: `return {} `, + ExpectedKeys: 0, + }, + { + Script: `return {"key1" BROKEN `, + ExpectedKeys: 0, + ExpectedError: true, + }, + } { - chM := make(chan prometheus.Metric) - go func() { - e.Collect(chM) - close(chM) - }() + e.options.LuaScript = []byte(tst.Script) + nKeys := tst.ExpectedKeys - for m := range chM { - if strings.Contains(m.Desc().String(), "test_script_value") { - nKeys-- + setupDBKeys(t, os.Getenv("TEST_REDIS_URI")) + defer deleteKeysFromDB(t, os.Getenv("TEST_REDIS_URI")) + + chM := make(chan prometheus.Metric) + go func() { + e.Collect(chM) + close(chM) + }() + scrapeErrorFound := false + + for m := range chM { + if strings.Contains(m.Desc().String(), "test_script_value") { + nKeys-- + } + + if strings.Contains(m.Desc().String(), "exporter_last_scrape_error") { + g := &dto.Metric{} + m.Write(g) + if g.GetGauge() != nil { + if *g.GetGauge().Value > 0 { + scrapeErrorFound = true + } + } + } } - } - if nKeys != 0 { - t.Error("didn't find expected script keys") + if nKeys != 0 { + t.Error("didn't find expected script keys") + } + + if tst.ExpectedError { + if !scrapeErrorFound { + t.Error("didn't find expected scrape errors") + } + } + } } @@ -928,46 +972,53 @@ func TestHTTPEndpoints(t *testing.T) { setupDBKeys(t, os.Getenv("TEST_PWD_REDIS_URI")) defer deleteKeysFromDB(t, os.Getenv("TEST_PWD_REDIS_URI")) + csk := dbNumStrFull + "=" + url.QueryEscape(keys[0]) + for _, tst := range []struct { addr string + ck string csk string pwd string target string }{ - {addr: os.Getenv("TEST_REDIS_URI")}, - {pwd: "", target: os.Getenv("TEST_REDIS_URI")}, - {pwd: "redis-password", target: os.Getenv("TEST_PWD_REDIS_URI")}, + {addr: os.Getenv("TEST_REDIS_URI"), csk: csk}, + {addr: os.Getenv("TEST_REDIS_URI"), ck: csk}, + {pwd: "", target: os.Getenv("TEST_REDIS_URI"), ck: csk}, + {pwd: "", target: os.Getenv("TEST_REDIS_URI"), csk: csk}, + {pwd: "redis-password", target: os.Getenv("TEST_PWD_REDIS_URI"), csk: csk}, } { name := fmt.Sprintf("%s---%s", tst.target, tst.pwd) - csk := dbNumStrFull + "=" + url.QueryEscape(keys[0]) t.Run(name, func(t *testing.T) { - r := prometheus.NewRegistry() - prometheus.DefaultGatherer = r - prometheus.DefaultRegisterer = r + options := ExporterOptions{ + Namespace: "test", + Password: tst.pwd, + LuaScript: []byte(`return {"a", "11", "b", "12", "c", "13"}`), + Registry: prometheus.NewRegistry(), + } - e, _ := NewRedisExporter(tst.addr, ExporterOptions{Namespace: "test", Password: tst.pwd, CheckSingleKeys: csk}) - prometheus.Register(e) + if tst.target == "" { + options.CheckSingleKeys = tst.csk + options.CheckKeys = tst.ck + } - var ts *httptest.Server + e, _ := NewRedisExporter(tst.addr, options) + ts := httptest.NewServer(e) - u := "" + u := ts.URL if tst.target != "" { - ts = httptest.NewServer(http.HandlerFunc(e.ScrapeHandler)) - u = ts.URL + u += "/scrape" v := url.Values{} v.Add("target", tst.target) - v.Add("check-single-keys", csk) + v.Add("check-single-keys", tst.csk) + v.Add("check-keys", tst.ck) up, _ := url.Parse(u) up.RawQuery = v.Encode() u = up.String() } else { - ts = httptest.NewServer(promhttp.Handler()) - u = ts.URL + "/metrics" + u += "/metrics" } - body := downloadURL(t, u) - wants := []string{ // metrics `test_connected_clients`, @@ -990,15 +1041,19 @@ func TestHTTPEndpoints(t *testing.T) { `standalone`, `cmd="config`, + `test_script_value`, // lua script + `test_key_size{db="db11",key="` + keys[0] + `"} 7`, `test_key_value{db="db11",key="` + keys[0] + `"} 1234.56`, `test_db_keys{db="db11"} `, `test_db_keys_expiring{db="db11"} `, } + + body := downloadURL(t, u) for _, want := range wants { if !strings.Contains(body, want) { - t.Errorf("want metrics to include %q, have:\n%s", want, body) + t.Errorf("url: %s want metrics to include %q, have:\n%s", u, want, body) break } } @@ -1011,10 +1066,9 @@ func TestSimultaneousRequests(t *testing.T) { setupDBKeys(t, os.Getenv("TEST_REDIS_URI")) defer deleteKeysFromDB(t, os.Getenv("TEST_REDIS_URI")) - e, _ := NewRedisExporter("", ExporterOptions{Namespace: "test", InclSystemMetrics: false}) - prometheus.Register(e) - var ts *httptest.Server - ts = httptest.NewServer(http.HandlerFunc(e.ScrapeHandler)) + e, _ := NewRedisExporter("", ExporterOptions{Namespace: "test", InclSystemMetrics: false, Registry: prometheus.NewRegistry()}) + ts := httptest.NewServer(e) + defer ts.Close() goroutines := 20 var wg sync.WaitGroup @@ -1030,10 +1084,11 @@ func TestSimultaneousRequests(t *testing.T) { } v.Add("target", target) v.Add("check-single-keys", dbNumStrFull+"="+url.QueryEscape(keys[0])) - up, _ := url.Parse(ts.URL) + up, _ := url.Parse(ts.URL + "/scrape") up.RawQuery = v.Encode() + fullURL := up.String() - body := downloadURL(t, up.String()) + body := downloadURL(t, fullURL) wants := []string{ `test_connected_clients`, `test_commands_processed_total`, @@ -1041,7 +1096,7 @@ func TestSimultaneousRequests(t *testing.T) { } for _, want := range wants { if !strings.Contains(body, want) { - t.Errorf("want metrics to include %q, have:\n%s", want, body) + t.Errorf("fullURL: %s - want metrics to include %q, have:\n%s", fullURL, want, body) break } } @@ -1106,16 +1161,10 @@ func TestSanitizeMetricName(t *testing.T) { } func TestKeysReset(t *testing.T) { - r := prometheus.NewRegistry() - prometheus.DefaultGatherer = r - prometheus.DefaultRegisterer = r - - ts := httptest.NewServer(promhttp.Handler()) + e, _ := NewRedisExporter(os.Getenv("TEST_REDIS_URI"), ExporterOptions{Namespace: "test", CheckSingleKeys: dbNumStrFull + "=" + keys[0], Registry: prometheus.NewRegistry()}) + ts := httptest.NewServer(e) defer ts.Close() - e, _ := NewRedisExporter(os.Getenv("TEST_REDIS_URI"), ExporterOptions{Namespace: "test", CheckSingleKeys: dbNumStrFull + "=" + keys[0]}) - prometheus.Register(e) - setupDBKeys(t, os.Getenv("TEST_REDIS_URI")) defer deleteKeysFromDB(t, os.Getenv("TEST_REDIS_URI")) @@ -1143,16 +1192,10 @@ func TestClusterMaster(t *testing.T) { t.Skipf("TEST_REDIS_CLUSTER_MASTER_URI not set - skipping") } - r := prometheus.NewRegistry() - prometheus.DefaultGatherer = r - prometheus.DefaultRegisterer = r - - ts := httptest.NewServer(promhttp.Handler()) - defer ts.Close() - addr := os.Getenv("TEST_REDIS_CLUSTER_MASTER_URI") - e, _ := NewRedisExporter(addr, ExporterOptions{Namespace: "test"}) - prometheus.Register(e) + e, _ := NewRedisExporter(addr, ExporterOptions{Namespace: "test", Registry: prometheus.NewRegistry()}) + ts := httptest.NewServer(e) + defer ts.Close() chM := make(chan prometheus.Metric, 10000) go func() { @@ -1176,14 +1219,12 @@ func TestPasswordProtectedInstance(t *testing.T) { if os.Getenv("TEST_PWD_REDIS_URI") == "" { t.Skipf("TEST_PWD_REDIS_URI not set - skipping") } - ts := httptest.NewServer(promhttp.Handler()) - defer ts.Close() - uri := os.Getenv("TEST_PWD_REDIS_URI") setupDBKeys(t, uri) - e, _ := NewRedisExporter(uri, ExporterOptions{Namespace: "test"}) - prometheus.Register(e) + e, _ := NewRedisExporter(uri, ExporterOptions{Namespace: "test", Registry: prometheus.NewRegistry()}) + ts := httptest.NewServer(e) + defer ts.Close() chM := make(chan prometheus.Metric, 10000) go func() { @@ -1202,18 +1243,13 @@ func TestPasswordInvalid(t *testing.T) { if os.Getenv("TEST_PWD_REDIS_URI") == "" { t.Skipf("TEST_PWD_REDIS_URI not set - skipping") } - r := prometheus.NewRegistry() - prometheus.DefaultGatherer = r - prometheus.DefaultRegisterer = r - - ts := httptest.NewServer(promhttp.Handler()) - defer ts.Close() testPwd := "redis-password" uri := strings.Replace(os.Getenv("TEST_PWD_REDIS_URI"), testPwd, "wrong-pwd", -1) - e, _ := NewRedisExporter(uri, ExporterOptions{Namespace: "test"}) - prometheus.Register(e) + e, _ := NewRedisExporter(uri, ExporterOptions{Namespace: "test", Registry: prometheus.NewRegistry()}) + ts := httptest.NewServer(e) + defer ts.Close() chM := make(chan prometheus.Metric, 10000) go func() { @@ -1233,16 +1269,10 @@ func TestClusterSlave(t *testing.T) { t.Skipf("TEST_REDIS_CLUSTER_SLAVE_URI not set - skipping") } - r := prometheus.NewRegistry() - prometheus.DefaultGatherer = r - prometheus.DefaultRegisterer = r - - ts := httptest.NewServer(promhttp.Handler()) - defer ts.Close() - addr := os.Getenv("TEST_REDIS_CLUSTER_SLAVE_URI") - e, _ := NewRedisExporter(addr, ExporterOptions{Namespace: "test"}) - prometheus.Register(e) + e, _ := NewRedisExporter(addr, ExporterOptions{Namespace: "test", Registry: prometheus.NewRegistry()}) + ts := httptest.NewServer(e) + defer ts.Close() chM := make(chan prometheus.Metric, 10000) go func() { @@ -1289,6 +1319,22 @@ func TestCheckKeys(t *testing.T) { } } +func TestHTTPIndexPage(t *testing.T) { + if os.Getenv("TEST_PWD_REDIS_URI") == "" { + t.Skipf("TEST_PWD_REDIS_URI not set - skipping") + } + + e, _ := NewRedisExporter(os.Getenv("TEST_PWD_REDIS_URI"), ExporterOptions{Namespace: "test", Registry: prometheus.NewRegistry()}) + ts := httptest.NewServer(e) + defer ts.Close() + + want := `Redis Exporter v` + body := downloadURL(t, ts.URL+"/") + if !strings.Contains(body, want) { + t.Errorf(`error, expected string "%s" in body, got body: \n\n%s`, want, body) + } +} + func init() { ll := strings.ToLower(os.Getenv("LOG_LEVEL")) if pl, err := log.ParseLevel(ll); err == nil { diff --git a/main.go b/main.go index e5f42444..9d668199 100644 --- a/main.go +++ b/main.go @@ -11,7 +11,6 @@ import ( "time" "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promhttp" log "github.com/sirupsen/logrus" ) @@ -99,6 +98,18 @@ func main() { tlsClientCertificates = append(tlsClientCertificates, cert) } + var ls []byte + if *scriptPath != "" { + if ls, err = ioutil.ReadFile(*scriptPath); err != nil { + log.Fatalf("Error loading script file %s err: %s", *scriptPath, err) + } + } + + registry := prometheus.NewRegistry() + if !*redisMetricsOnly { + registry = prometheus.DefaultRegisterer.(*prometheus.Registry) + } + exp, err := NewRedisExporter( *redisAddr, ExporterOptions{ @@ -107,55 +118,23 @@ func main() { ConfigCommandName: *configCommand, CheckKeys: *checkKeys, CheckSingleKeys: *checkSingleKeys, + LuaScript: ls, InclSystemMetrics: *inclSystemMetrics, IsTile38: *isTile38, ExportClientList: *exportClientList, SkipTLSVerification: *skipTLSVerification, ClientCertificates: tlsClientCertificates, ConnectionTimeouts: to, + MetricsPath: *metricPath, + RedisMetricsOnly: *redisMetricsOnly, + Registry: registry, }, ) if err != nil { log.Fatal(err) } - if *scriptPath != "" { - if exp.LuaScript, err = ioutil.ReadFile(*scriptPath); err != nil { - log.Fatalf("Error loading script file %s err: %s", *scriptPath, err) - } - } - - buildInfo := prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Name: "redis_exporter_build_info", - Help: "redis exporter build_info", - }, []string{"version", "commit_sha", "build_date", "golang_version"}) - buildInfo.WithLabelValues(BuildVersion, BuildCommitSha, BuildDate, runtime.Version()).Set(1) - - if *redisMetricsOnly { - registry := prometheus.NewRegistry() - registry.MustRegister(exp) - handler := promhttp.HandlerFor(registry, promhttp.HandlerOpts{}) - http.Handle(*metricPath, handler) - } else { - prometheus.MustRegister(exp) - prometheus.MustRegister(buildInfo) - http.Handle(*metricPath, promhttp.Handler()) - } - - http.HandleFunc("/scrape", exp.ScrapeHandler) - - http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(`<html> -<head><title>Redis Exporter v` + BuildVersion + ` - -

Redis Exporter ` + BuildVersion + `

-

Metrics

- - -`)) - }) - log.Infof("Providing metrics at %s%s", *listenAddress, *metricPath) log.Debugf("Configured redis addr: %#v", *redisAddr) - log.Fatal(http.ListenAndServe(*listenAddress, nil)) + log.Fatal(http.ListenAndServe(*listenAddress, exp)) }