From f3a70226025bf81e55eaf9d7386f9f7b3a267694 Mon Sep 17 00:00:00 2001 From: Siavash Safi Date: Sat, 14 Oct 2017 14:23:42 +0200 Subject: [PATCH] Add `collect[]` parameter (#699) * Add `collect[]` parameter * Add TODo comment about staticcheck ignored * Restore promhttp.HandlerOpts * Log a warning and return HTTP error instead of failing * Check collector existence and status, cleanups * Fix warnings and error messages * Don't panic, return error if collector registration failed * Update README --- Makefile | 6 +++++- README.md | 33 ++++++++++++++++++++++++++++ collector/collector.go | 17 +++++++++++++-- node_exporter.go | 49 ++++++++++++++++++++++++++++++++---------- 4 files changed, 91 insertions(+), 14 deletions(-) diff --git a/Makefile b/Makefile index 4f5ebe0c..93c5b6c6 100644 --- a/Makefile +++ b/Makefile @@ -27,6 +27,10 @@ DOCKER_IMAGE_TAG ?= $(subst /,-,$(shell git rev-parse --abbrev-ref HEAD)) MACH ?= $(shell uname -m) DOCKERFILE ?= Dockerfile +# TODO: Remove deprecated and problematic InstrumentHandlerFunc usage. +STATICCHECK_IGNORE = \ + github.com/prometheus/node_exporter/node_exporter.go:SA1019 + ifeq ($(GOHOSTARCH),amd64) # Only supported on amd64 test-flags := -race @@ -99,7 +103,7 @@ vet: staticcheck: $(STATICCHECK) @echo ">> running staticcheck" - @$(STATICCHECK) $(pkgs) + @$(STATICCHECK) -ignore "$(STATICCHECK_IGNORE)" $(pkgs) build: $(PROMU) @echo ">> building binaries" diff --git a/README.md b/README.md index 04053a1c..d4ef98aa 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,39 @@ echo 'role{role="application_server"} 1' > /path/to/directory/role.prom.$$ mv /path/to/directory/role.prom.$$ /path/to/directory/role.prom ``` +### Filtering enabled collectors + +The node_exporter will expose all metrics from enabled collectors by default, but it can be passed an optional list of collectors to filter metrics. The `collect[]` parameter accepts values matching enabled collector names. + +This can be useful for specifying different scrape intervals for different collectors in Prometheus: + +```yaml +scrape_configs: + - job_name: 'node resources' + scrape_interval: 15s + static_configs: + - targets: + - '192.168.1.2:9100' + params: + collect[]: + - cpu + - meminfo + - diskstats + - netdev + - netstat + + - job_name: 'node storage' + scrape_interval: 1m + static_configs: + - targets: + - '192.168.1.2:9100' + params: + collect[]: + - filefd + - filesystem + - xfs +``` + ## Building and running Prerequisites: diff --git a/collector/collector.go b/collector/collector.go index 1176b6f3..e9a78f04 100644 --- a/collector/collector.go +++ b/collector/collector.go @@ -80,7 +80,18 @@ type nodeCollector struct { } // NewNodeCollector creates a new NodeCollector -func NewNodeCollector() (*nodeCollector, error) { +func NewNodeCollector(filters ...string) (*nodeCollector, error) { + f := make(map[string]bool) + for _, filter := range filters { + enabled, exist := collectorState[filter] + if !exist { + return nil, fmt.Errorf("missing collector: %s", filter) + } + if !*enabled { + return nil, fmt.Errorf("disabled collector: %s", filter) + } + f[filter] = true + } collectors := make(map[string]Collector) for key, enabled := range collectorState { if *enabled { @@ -88,7 +99,9 @@ func NewNodeCollector() (*nodeCollector, error) { if err != nil { return nil, err } - collectors[key] = collector + if len(f) == 0 || f[key] { + collectors[key] = collector + } } } return &nodeCollector{Collectors: collectors}, nil diff --git a/node_exporter.go b/node_exporter.go index 10771882..7c1958ee 100644 --- a/node_exporter.go +++ b/node_exporter.go @@ -14,6 +14,7 @@ package main import ( + "fmt" "net/http" _ "net/http/pprof" @@ -29,6 +30,40 @@ func init() { prometheus.MustRegister(version.NewCollector("node_exporter")) } +func handler(w http.ResponseWriter, r *http.Request) { + filters := r.URL.Query()["collect[]"] + log.Debugln("collect query:", filters) + + nc, err := collector.NewNodeCollector(filters...) + if err != nil { + log.Warnln("Couldn't create", err) + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(fmt.Sprintf("Couldn't create %s", err))) + return + } + + registry := prometheus.NewRegistry() + err = registry.Register(nc) + if err != nil { + log.Errorln("Couldn't register collector:", err) + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Couldn't register collector: %s", err))) + return + } + + gatherers := prometheus.Gatherers{ + prometheus.DefaultGatherer, + registry, + } + // Delegate http serving to Prometheus client library, which will call collector.Collect. + h := promhttp.HandlerFor(gatherers, + promhttp.HandlerOpts{ + ErrorLog: log.NewErrorLogger(), + ErrorHandling: promhttp.ContinueOnError, + }) + h.ServeHTTP(w, r) +} + func main() { var ( listenAddress = kingpin.Flag("web.listen-address", "Address on which to expose metrics and web interface.").Default(":9100").String() @@ -43,6 +78,7 @@ func main() { log.Infoln("Starting node_exporter", version.Info()) log.Infoln("Build context", version.BuildContext()) + // This instance is only used to check collector creation and logging. nc, err := collector.NewNodeCollector() if err != nil { log.Fatalf("Couldn't create collector: %s", err) @@ -52,17 +88,8 @@ func main() { log.Infof(" - %s", n) } - if err := prometheus.Register(nc); err != nil { - log.Fatalf("Couldn't register collector: %s", err) - } - handler := promhttp.HandlerFor(prometheus.DefaultGatherer, - promhttp.HandlerOpts{ - ErrorLog: log.NewErrorLogger(), - ErrorHandling: promhttp.ContinueOnError, - }) - - // TODO(ts): Remove deprecated and problematic InstrumentHandler usage. - http.Handle(*metricsPath, prometheus.InstrumentHandler("prometheus", handler)) + // TODO(ts): Remove deprecated and problematic InstrumentHandlerFunc usage. + http.HandleFunc(*metricsPath, prometheus.InstrumentHandlerFunc("prometheus", handler)) http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(` Node Exporter