mirror of
				https://github.com/prometheus/node_exporter.git
				synced 2025-08-20 18:33:52 -07:00 
			
		
		
		
	This PR adds a `exclude[]` URL parameter to exclude specific enabled collectors. Compared to `collect[]` parameter, the `exclude[]` parameter results in a filtered list which equals enabled collectors minus excluded ones. Signed-off-by: Siavash Safi <git@hosted.run>
		
			
				
	
	
		
			251 lines
		
	
	
		
			8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			251 lines
		
	
	
		
			8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2015 The Prometheus Authors
 | |
| // Licensed under the Apache License, Version 2.0 (the "License");
 | |
| // you may not use this file except in compliance with the License.
 | |
| // You may obtain a copy of the License at
 | |
| //
 | |
| // http://www.apache.org/licenses/LICENSE-2.0
 | |
| //
 | |
| // Unless required by applicable law or agreed to in writing, software
 | |
| // distributed under the License is distributed on an "AS IS" BASIS,
 | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| // See the License for the specific language governing permissions and
 | |
| // limitations under the License.
 | |
| 
 | |
| package main
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"log/slog"
 | |
| 	"net/http"
 | |
| 	_ "net/http/pprof"
 | |
| 	"os"
 | |
| 	"os/user"
 | |
| 	"runtime"
 | |
| 	"slices"
 | |
| 	"sort"
 | |
| 
 | |
| 	"github.com/prometheus/common/promslog"
 | |
| 	"github.com/prometheus/common/promslog/flag"
 | |
| 
 | |
| 	"github.com/alecthomas/kingpin/v2"
 | |
| 	"github.com/prometheus/client_golang/prometheus"
 | |
| 	promcollectors "github.com/prometheus/client_golang/prometheus/collectors"
 | |
| 	versioncollector "github.com/prometheus/client_golang/prometheus/collectors/version"
 | |
| 	"github.com/prometheus/client_golang/prometheus/promhttp"
 | |
| 	"github.com/prometheus/common/version"
 | |
| 	"github.com/prometheus/exporter-toolkit/web"
 | |
| 	"github.com/prometheus/exporter-toolkit/web/kingpinflag"
 | |
| 	"github.com/prometheus/node_exporter/collector"
 | |
| )
 | |
| 
 | |
| // handler wraps an unfiltered http.Handler but uses a filtered handler,
 | |
| // created on the fly, if filtering is requested. Create instances with
 | |
| // newHandler.
 | |
| type handler struct {
 | |
| 	unfilteredHandler http.Handler
 | |
| 	// enabledCollectors list is used for logging and filtering
 | |
| 	enabledCollectors []string
 | |
| 	// exporterMetricsRegistry is a separate registry for the metrics about
 | |
| 	// the exporter itself.
 | |
| 	exporterMetricsRegistry *prometheus.Registry
 | |
| 	includeExporterMetrics  bool
 | |
| 	maxRequests             int
 | |
| 	logger                  *slog.Logger
 | |
| }
 | |
| 
 | |
| func newHandler(includeExporterMetrics bool, maxRequests int, logger *slog.Logger) *handler {
 | |
| 	h := &handler{
 | |
| 		exporterMetricsRegistry: prometheus.NewRegistry(),
 | |
| 		includeExporterMetrics:  includeExporterMetrics,
 | |
| 		maxRequests:             maxRequests,
 | |
| 		logger:                  logger,
 | |
| 	}
 | |
| 	if h.includeExporterMetrics {
 | |
| 		h.exporterMetricsRegistry.MustRegister(
 | |
| 			promcollectors.NewProcessCollector(promcollectors.ProcessCollectorOpts{}),
 | |
| 			promcollectors.NewGoCollector(),
 | |
| 		)
 | |
| 	}
 | |
| 	if innerHandler, err := h.innerHandler(); err != nil {
 | |
| 		panic(fmt.Sprintf("Couldn't create metrics handler: %s", err))
 | |
| 	} else {
 | |
| 		h.unfilteredHandler = innerHandler
 | |
| 	}
 | |
| 	return h
 | |
| }
 | |
| 
 | |
| // ServeHTTP implements http.Handler.
 | |
| func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | |
| 	collects := r.URL.Query()["collect[]"]
 | |
| 	h.logger.Debug("collect query:", "collects", collects)
 | |
| 
 | |
| 	excludes := r.URL.Query()["exclude[]"]
 | |
| 	h.logger.Debug("exclude query:", "excludes", excludes)
 | |
| 
 | |
| 	if len(collects) == 0 && len(excludes) == 0 {
 | |
| 		// No filters, use the prepared unfiltered handler.
 | |
| 		h.unfilteredHandler.ServeHTTP(w, r)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	if len(collects) > 0 && len(excludes) > 0 {
 | |
| 		h.logger.Debug("rejecting combined collect and exclude queries")
 | |
| 		w.WriteHeader(http.StatusBadRequest)
 | |
| 		w.Write([]byte("Combined collect and exclude queries are not allowed."))
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	filters := &collects
 | |
| 	if len(excludes) > 0 {
 | |
| 		// In exclude mode, filtered collectors = enabled - excludeed.
 | |
| 		f := []string{}
 | |
| 		for _, c := range h.enabledCollectors {
 | |
| 			if (slices.Index(excludes, c)) == -1 {
 | |
| 				f = append(f, c)
 | |
| 			}
 | |
| 		}
 | |
| 		filters = &f
 | |
| 	}
 | |
| 
 | |
| 	// To serve filtered metrics, we create a filtering handler on the fly.
 | |
| 	filteredHandler, err := h.innerHandler(*filters...)
 | |
| 	if err != nil {
 | |
| 		h.logger.Warn("Couldn't create filtered metrics handler:", "err", err)
 | |
| 		w.WriteHeader(http.StatusBadRequest)
 | |
| 		w.Write([]byte(fmt.Sprintf("Couldn't create filtered metrics handler: %s", err)))
 | |
| 		return
 | |
| 	}
 | |
| 	filteredHandler.ServeHTTP(w, r)
 | |
| }
 | |
| 
 | |
| // innerHandler is used to create both the one unfiltered http.Handler to be
 | |
| // wrapped by the outer handler and also the filtered handlers created on the
 | |
| // fly. The former is accomplished by calling innerHandler without any arguments
 | |
| // (in which case it will log all the collectors enabled via command-line
 | |
| // flags).
 | |
| func (h *handler) innerHandler(filters ...string) (http.Handler, error) {
 | |
| 	nc, err := collector.NewNodeCollector(h.logger, filters...)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("couldn't create collector: %s", err)
 | |
| 	}
 | |
| 
 | |
| 	// Only log the creation of an unfiltered handler, which should happen
 | |
| 	// only once upon startup.
 | |
| 	if len(filters) == 0 {
 | |
| 		h.logger.Info("Enabled collectors")
 | |
| 		for n := range nc.Collectors {
 | |
| 			h.enabledCollectors = append(h.enabledCollectors, n)
 | |
| 		}
 | |
| 		sort.Strings(h.enabledCollectors)
 | |
| 		for _, c := range h.enabledCollectors {
 | |
| 			h.logger.Info(c)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	r := prometheus.NewRegistry()
 | |
| 	r.MustRegister(versioncollector.NewCollector("node_exporter"))
 | |
| 	if err := r.Register(nc); err != nil {
 | |
| 		return nil, fmt.Errorf("couldn't register node collector: %s", err)
 | |
| 	}
 | |
| 
 | |
| 	var handler http.Handler
 | |
| 	if h.includeExporterMetrics {
 | |
| 		handler = promhttp.HandlerFor(
 | |
| 			prometheus.Gatherers{h.exporterMetricsRegistry, r},
 | |
| 			promhttp.HandlerOpts{
 | |
| 				ErrorLog:            slog.NewLogLogger(h.logger.Handler(), slog.LevelError),
 | |
| 				ErrorHandling:       promhttp.ContinueOnError,
 | |
| 				MaxRequestsInFlight: h.maxRequests,
 | |
| 				Registry:            h.exporterMetricsRegistry,
 | |
| 			},
 | |
| 		)
 | |
| 		// Note that we have to use h.exporterMetricsRegistry here to
 | |
| 		// use the same promhttp metrics for all expositions.
 | |
| 		handler = promhttp.InstrumentMetricHandler(
 | |
| 			h.exporterMetricsRegistry, handler,
 | |
| 		)
 | |
| 	} else {
 | |
| 		handler = promhttp.HandlerFor(
 | |
| 			r,
 | |
| 			promhttp.HandlerOpts{
 | |
| 				ErrorLog:            slog.NewLogLogger(h.logger.Handler(), slog.LevelError),
 | |
| 				ErrorHandling:       promhttp.ContinueOnError,
 | |
| 				MaxRequestsInFlight: h.maxRequests,
 | |
| 			},
 | |
| 		)
 | |
| 	}
 | |
| 
 | |
| 	return handler, nil
 | |
| }
 | |
| 
 | |
| func main() {
 | |
| 	var (
 | |
| 		metricsPath = kingpin.Flag(
 | |
| 			"web.telemetry-path",
 | |
| 			"Path under which to expose metrics.",
 | |
| 		).Default("/metrics").String()
 | |
| 		disableExporterMetrics = kingpin.Flag(
 | |
| 			"web.disable-exporter-metrics",
 | |
| 			"Exclude metrics about the exporter itself (promhttp_*, process_*, go_*).",
 | |
| 		).Bool()
 | |
| 		maxRequests = kingpin.Flag(
 | |
| 			"web.max-requests",
 | |
| 			"Maximum number of parallel scrape requests. Use 0 to disable.",
 | |
| 		).Default("40").Int()
 | |
| 		disableDefaultCollectors = kingpin.Flag(
 | |
| 			"collector.disable-defaults",
 | |
| 			"Set all collectors to disabled by default.",
 | |
| 		).Default("false").Bool()
 | |
| 		maxProcs = kingpin.Flag(
 | |
| 			"runtime.gomaxprocs", "The target number of CPUs Go will run on (GOMAXPROCS)",
 | |
| 		).Envar("GOMAXPROCS").Default("1").Int()
 | |
| 		toolkitFlags = kingpinflag.AddFlags(kingpin.CommandLine, ":9100")
 | |
| 	)
 | |
| 
 | |
| 	promslogConfig := &promslog.Config{}
 | |
| 	flag.AddFlags(kingpin.CommandLine, promslogConfig)
 | |
| 	kingpin.Version(version.Print("node_exporter"))
 | |
| 	kingpin.CommandLine.UsageWriter(os.Stdout)
 | |
| 	kingpin.HelpFlag.Short('h')
 | |
| 	kingpin.Parse()
 | |
| 	logger := promslog.New(promslogConfig)
 | |
| 
 | |
| 	if *disableDefaultCollectors {
 | |
| 		collector.DisableDefaultCollectors()
 | |
| 	}
 | |
| 	logger.Info("Starting node_exporter", "version", version.Info())
 | |
| 	logger.Info("Build context", "build_context", version.BuildContext())
 | |
| 	if user, err := user.Current(); err == nil && user.Uid == "0" {
 | |
| 		logger.Warn("Node Exporter is running as root user. This exporter is designed to run as unprivileged user, root is not required.")
 | |
| 	}
 | |
| 	runtime.GOMAXPROCS(*maxProcs)
 | |
| 	logger.Debug("Go MAXPROCS", "procs", runtime.GOMAXPROCS(0))
 | |
| 
 | |
| 	http.Handle(*metricsPath, newHandler(!*disableExporterMetrics, *maxRequests, logger))
 | |
| 	if *metricsPath != "/" {
 | |
| 		landingConfig := web.LandingConfig{
 | |
| 			Name:        "Node Exporter",
 | |
| 			Description: "Prometheus Node Exporter",
 | |
| 			Version:     version.Info(),
 | |
| 			Links: []web.LandingLinks{
 | |
| 				{
 | |
| 					Address: *metricsPath,
 | |
| 					Text:    "Metrics",
 | |
| 				},
 | |
| 			},
 | |
| 		}
 | |
| 		landingPage, err := web.NewLandingPage(landingConfig)
 | |
| 		if err != nil {
 | |
| 			logger.Error(err.Error())
 | |
| 			os.Exit(1)
 | |
| 		}
 | |
| 		http.Handle("/", landingPage)
 | |
| 	}
 | |
| 
 | |
| 	server := &http.Server{}
 | |
| 	if err := web.ListenAndServe(server, toolkitFlags, logger); err != nil {
 | |
| 		logger.Error(err.Error())
 | |
| 		os.Exit(1)
 | |
| 	}
 | |
| }
 |