|
|
|
// 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"
|
|
|
|
"github.com/prometheus/common/promlog"
|
|
|
|
"github.com/prometheus/common/promlog/flag"
|
|
|
|
"net/http"
|
|
|
|
_ "net/http/pprof"
|
|
|
|
"os"
|
|
|
|
"sort"
|
|
|
|
|
|
|
|
"github.com/go-kit/kit/log"
|
|
|
|
"github.com/go-kit/kit/log/level"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
|
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
|
|
|
"github.com/prometheus/common/version"
|
|
|
|
"github.com/prometheus/node_exporter/collector"
|
|
|
|
"github.com/prometheus/node_exporter/https"
|
|
|
|
"gopkg.in/alecthomas/kingpin.v2"
|
|
|
|
)
|
|
|
|
|
|
|
|
// 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
|
|
|
|
// exporterMetricsRegistry is a separate registry for the metrics about
|
|
|
|
// the exporter itself.
|
|
|
|
exporterMetricsRegistry *prometheus.Registry
|
|
|
|
includeExporterMetrics bool
|
|
|
|
maxRequests int
|
|
|
|
logger log.Logger
|
|
|
|
}
|
|
|
|
|
|
|
|
func newHandler(includeExporterMetrics bool, maxRequests int, logger log.Logger) *handler {
|
|
|
|
h := &handler{
|
|
|
|
exporterMetricsRegistry: prometheus.NewRegistry(),
|
|
|
|
includeExporterMetrics: includeExporterMetrics,
|
|
|
|
maxRequests: maxRequests,
|
|
|
|
logger: logger,
|
|
|
|
}
|
|
|
|
if h.includeExporterMetrics {
|
|
|
|
h.exporterMetricsRegistry.MustRegister(
|
|
|
|
prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{}),
|
|
|
|
prometheus.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) {
|
|
|
|
filters := r.URL.Query()["collect[]"]
|
|
|
|
level.Debug(h.logger).Log("msg", "collect query:", "filters", filters)
|
|
|
|
|
|
|
|
if len(filters) == 0 {
|
|
|
|
// No filters, use the prepared unfiltered handler.
|
|
|
|
h.unfilteredHandler.ServeHTTP(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// To serve filtered metrics, we create a filtering handler on the fly.
|
|
|
|
filteredHandler, err := h.innerHandler(filters...)
|
|
|
|
if err != nil {
|
|
|
|
level.Warn(h.logger).Log("msg", "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 {
|
|
|
|
level.Info(h.logger).Log("msg", "Enabled collectors")
|
|
|
|
collectors := []string{}
|
|
|
|
for n := range nc.Collectors {
|
|
|
|
collectors = append(collectors, n)
|
|
|
|
}
|
|
|
|
sort.Strings(collectors)
|
|
|
|
for _, c := range collectors {
|
|
|
|
level.Info(h.logger).Log("collector", c)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
r := prometheus.NewRegistry()
|
|
|
|
r.MustRegister(version.NewCollector("node_exporter"))
|
|
|
|
if err := r.Register(nc); err != nil {
|
|
|
|
return nil, fmt.Errorf("couldn't register node collector: %s", err)
|
|
|
|
}
|
|
|
|
handler := promhttp.HandlerFor(
|
|
|
|
prometheus.Gatherers{h.exporterMetricsRegistry, r},
|
|
|
|
promhttp.HandlerOpts{
|
|
|
|
ErrorHandling: promhttp.ContinueOnError,
|
|
|
|
MaxRequestsInFlight: h.maxRequests,
|
|
|
|
Registry: h.exporterMetricsRegistry,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
if h.includeExporterMetrics {
|
|
|
|
// Note that we have to use h.exporterMetricsRegistry here to
|
|
|
|
// use the same promhttp metrics for all expositions.
|
|
|
|
handler = promhttp.InstrumentMetricHandler(
|
|
|
|
h.exporterMetricsRegistry, handler,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
return handler, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
var (
|
|
|
|
listenAddress = kingpin.Flag(
|
|
|
|
"web.listen-address",
|
|
|
|
"Address on which to expose metrics and web interface.",
|
|
|
|
).Default(":9100").String()
|
|
|
|
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()
|
|
|
|
configFile = kingpin.Flag(
|
|
|
|
"web.config",
|
|
|
|
"Path to config yaml file that can enable TLS or authentication.",
|
|
|
|
).Default("").String()
|
|
|
|
)
|
|
|
|
|
|
|
|
promlogConfig := &promlog.Config{}
|
|
|
|
flag.AddFlags(kingpin.CommandLine, promlogConfig)
|
|
|
|
kingpin.Version(version.Print("node_exporter"))
|
|
|
|
kingpin.HelpFlag.Short('h')
|
|
|
|
kingpin.Parse()
|
|
|
|
logger := promlog.New(promlogConfig)
|
|
|
|
|
|
|
|
level.Info(logger).Log("msg", "Starting node_exporter", "version", version.Info())
|
|
|
|
level.Info(logger).Log("msg", "Build context", "build_context", version.BuildContext())
|
|
|
|
|
|
|
|
http.Handle(*metricsPath, newHandler(!*disableExporterMetrics, *maxRequests, logger))
|
|
|
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
w.Write([]byte(`<html>
|
|
|
|
<head><title>Node Exporter</title></head>
|
|
|
|
<body>
|
|
|
|
<h1>Node Exporter</h1>
|
|
|
|
<p><a href="` + *metricsPath + `">Metrics</a></p>
|
|
|
|
</body>
|
|
|
|
</html>`))
|
|
|
|
})
|
|
|
|
|
|
|
|
level.Info(logger).Log("msg", "Listening on", "address", *listenAddress)
|
|
|
|
server := &http.Server{Addr: *listenAddress}
|
|
|
|
if err := https.Listen(server, *configFile); err != nil {
|
|
|
|
level.Error(logger).Log("err", err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
}
|