Create custom metrics endpoint to read timeout from request header

This commit is contained in:
Calle Pettersson
2019-06-23 22:01:43 +02:00
parent daa6f3d111
commit 411954cf9d

View File

@@ -5,7 +5,9 @@ package main
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"os"
"sort" "sort"
"strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
@@ -52,6 +54,12 @@ var (
[]string{"collector"}, []string{"collector"},
nil, nil,
) )
snapshotDuration = prometheus.NewDesc(
prometheus.BuildFQName(collector.Namespace, "exporter", "perflib_snapshot_duration_seconds"),
"Duration of perflib snapshot capture",
nil,
nil,
)
// This can be removed when client_golang exposes this on Windows // This can be removed when client_golang exposes this on Windows
// (See https://github.com/prometheus/client_golang/issues/376) // (See https://github.com/prometheus/client_golang/issues/376)
@@ -74,7 +82,13 @@ func (coll WmiCollector) Describe(ch chan<- *prometheus.Desc) {
// Collect sends the collected metrics from each of the collectors to // Collect sends the collected metrics from each of the collectors to
// prometheus. // prometheus.
func (coll WmiCollector) Collect(ch chan<- prometheus.Metric) { func (coll WmiCollector) Collect(ch chan<- prometheus.Metric) {
t := time.Now()
scrapeContext, err := collector.PrepareScrapeContext() scrapeContext, err := collector.PrepareScrapeContext()
ch <- prometheus.MustNewConstMetric(
snapshotDuration,
prometheus.GaugeValue,
time.Since(t).Seconds(),
)
if err != nil { if err != nil {
ch <- prometheus.NewInvalidMetric(scrapeSuccessDesc, fmt.Errorf("failed to prepare scrape: %v", err)) ch <- prometheus.NewInvalidMetric(scrapeSuccessDesc, fmt.Errorf("failed to prepare scrape: %v", err))
return return
@@ -91,8 +105,8 @@ func (coll WmiCollector) Collect(ch chan<- prometheus.Metric) {
go func() { go func() {
for { for {
select { select {
case m := <-metricsBuffer: case m, ok := <-metricsBuffer:
if !stopped { if ok && !stopped {
ch <- m ch <- m
} }
case <-allDone: case <-allDone:
@@ -111,8 +125,8 @@ func (coll WmiCollector) Collect(ch chan<- prometheus.Metric) {
for name, c := range coll.collectors { for name, c := range coll.collectors {
go func(name string, c collector.Collector) { go func(name string, c collector.Collector) {
defer wg.Done()
execute(name, c, scrapeContext, metricsBuffer) execute(name, c, scrapeContext, metricsBuffer)
wg.Done()
delete(remainingCollectors, name) delete(remainingCollectors, name)
}(name, c) }(name, c)
} }
@@ -229,10 +243,6 @@ func loadCollectors(list string) (map[string]collector.Collector, error) {
return collectors, nil return collectors, nil
} }
func init() {
prometheus.MustRegister(version.NewCollector("wmi_exporter"))
}
func initWbem() { func initWbem() {
// This initialization prevents a memory leak on WMF 5+. See // This initialization prevents a memory leak on WMF 5+. See
// https://github.com/martinlindhe/wmi_exporter/issues/77 and linked issues // https://github.com/martinlindhe/wmi_exporter/issues/77 and linked issues
@@ -264,10 +274,10 @@ func main() {
"collectors.print", "collectors.print",
"If true, print available collectors and exit.", "If true, print available collectors and exit.",
).Bool() ).Bool()
maxScrapeDuration = kingpin.Flag( timeoutMargin = kingpin.Flag(
"scrape.max-duration", "scrape.timeout-margin",
"Time after which collectors are aborted during a scrape", "Seconds to subtract from the timeout allowed by the client. Tune to allow for overhead or high loads.",
).Default("30s").Duration() ).Default("0.5").Float64()
) )
log.AddFlags(kingpin.CommandLine) log.AddFlags(kingpin.CommandLine)
@@ -312,13 +322,17 @@ func main() {
log.Infof("Enabled collectors: %v", strings.Join(keys(collectors), ", ")) log.Infof("Enabled collectors: %v", strings.Join(keys(collectors), ", "))
exporter := WmiCollector{ h := &metricsHandler{
collectors: collectors, timeoutMargin: *timeoutMargin,
maxScrapeDuration: *maxScrapeDuration, collectorFactory: func(timeout time.Duration) *WmiCollector {
return &WmiCollector{
collectors: collectors,
maxScrapeDuration: timeout,
}
},
} }
prometheus.MustRegister(exporter)
http.Handle(*metricsPath, promhttp.Handler()) http.Handle(*metricsPath, h)
http.HandleFunc("/health", healthCheck) http.HandleFunc("/health", healthCheck)
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, *metricsPath, http.StatusMovedPermanently) http.Redirect(w, r, *metricsPath, http.StatusMovedPermanently)
@@ -382,3 +396,36 @@ loop:
changes <- svc.Status{State: svc.StopPending} changes <- svc.Status{State: svc.StopPending}
return return
} }
type metricsHandler struct {
timeoutMargin float64
collectorFactory func(timeout time.Duration) *WmiCollector
}
func (mh *metricsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
const defaultTimeout = 10.0
var timeoutSeconds float64
if v := r.Header.Get("X-Prometheus-Scrape-Timeout-Seconds"); v != "" {
var err error
timeoutSeconds, err = strconv.ParseFloat(v, 64)
if err != nil {
log.Warnf("Couldn't parse X-Prometheus-Scrape-Timeout-Seconds: %q. Defaulting timeout to %d", v, defaultTimeout)
}
}
if timeoutSeconds == 0 {
timeoutSeconds = defaultTimeout
}
timeoutSeconds = timeoutSeconds - mh.timeoutMargin
reg := prometheus.NewRegistry()
reg.MustRegister(mh.collectorFactory(time.Duration(timeoutSeconds * float64(time.Second))))
reg.MustRegister(
prometheus.NewProcessCollector(os.Getpid(), ""),
prometheus.NewGoCollector(),
version.NewCollector("wmi_exporter"),
)
h := promhttp.HandlerFor(reg, promhttp.HandlerOpts{})
h.ServeHTTP(w, r)
}