Repository: prometheus/blackbox_exporter
Pull request #1060 (2023-04-19 13:37:07)

Explicit registration of internal exporter metrics
- The first attempt to de-register internal metrics in #1056 was not sucessful.
- Added a `NewSafeConfig` function that gets a `prometheus.Registerer` as parameter and register config reloading metrics.
- Moved `moduleUnknownCounter` again to main and now it's passed (optionally) to `Handler` function.


Patch:
diff --git a/config/config.go b/config/config.go
index 1ce6ac9..b665a6e 100644
--- a/config/config.go
+++ b/config/config.go
@@ -39,18 +39,6 @@ import (
 )
 
 var (
-	configReloadSuccess = promauto.NewGauge(prometheus.GaugeOpts{
-		Namespace: "blackbox_exporter",
-		Name:      "config_last_reload_successful",
-		Help:      "Blackbox exporter config loaded successfully.",
-	})
-
-	configReloadSeconds = promauto.NewGauge(prometheus.GaugeOpts{
-		Namespace: "blackbox_exporter",
-		Name:      "config_last_reload_success_timestamp_seconds",
-		Help:      "Timestamp of the last successful configuration reload.",
-	})
-
 	// DefaultModule set default configuration for the Module
 	DefaultModule = Module{
 		HTTP: DefaultHTTPProbe,
@@ -96,17 +84,34 @@ type Config struct {
 
 type SafeConfig struct {
 	sync.RWMutex
-	C *Config
+	C                   *Config
+	configReloadSuccess prometheus.Gauge
+	configReloadSeconds prometheus.Gauge
+}
+
+func NewSafeConfig(reg prometheus.Registerer) *SafeConfig {
+	configReloadSuccess := promauto.With(reg).NewGauge(prometheus.GaugeOpts{
+		Namespace: "blackbox_exporter",
+		Name:      "config_last_reload_successful",
+		Help:      "Blackbox exporter config loaded successfully.",
+	})
+
+	configReloadSeconds := promauto.With(reg).NewGauge(prometheus.GaugeOpts{
+		Namespace: "blackbox_exporter",
+		Name:      "config_last_reload_success_timestamp_seconds",
+		Help:      "Timestamp of the last successful configuration reload.",
+	})
+	return &SafeConfig{C: &Config{}, configReloadSuccess: configReloadSuccess, configReloadSeconds: configReloadSeconds}
 }
 
 func (sc *SafeConfig) ReloadConfig(confFile string, logger log.Logger) (err error) {
 	var c = &Config{}
 	defer func() {
 		if err != nil {
-			configReloadSuccess.Set(0)
+			sc.configReloadSuccess.Set(0)
 		} else {
-			configReloadSuccess.Set(1)
-			configReloadSeconds.SetToCurrentTime()
+			sc.configReloadSuccess.Set(1)
+			sc.configReloadSeconds.SetToCurrentTime()
 		}
 	}()
 
diff --git a/main.go b/main.go
index efd0c57..8f87d2f 100644
--- a/main.go
+++ b/main.go
@@ -31,6 +31,7 @@ import (
 	"github.com/go-kit/log/level"
 	"github.com/pkg/errors"
 	"github.com/prometheus/client_golang/prometheus"
+	"github.com/prometheus/client_golang/prometheus/promauto"
 	"github.com/prometheus/client_golang/prometheus/promhttp"
 	"github.com/prometheus/common/promlog"
 	"github.com/prometheus/common/promlog/flag"
@@ -44,9 +45,7 @@ import (
 )
 
 var (
-	sc = &config.SafeConfig{
-		C: &config.Config{},
-	}
+	sc = config.NewSafeConfig(prometheus.DefaultRegisterer)
 
 	configFile    = kingpin.Flag("config.file", "Blackbox exporter configuration file.").Default("blackbox.yml").String()
 	timeoutOffset = kingpin.Flag("timeout-offset", "Offset to subtract from timeout in seconds.").Default("0.5").Float64()
@@ -55,6 +54,11 @@ var (
 	externalURL   = kingpin.Flag("web.external-url", "The URL under which Blackbox exporter is externally reachable (for example, if Blackbox exporter is served via a reverse proxy). Used for generating relative and absolute links back to Blackbox exporter itself. If the URL has a path portion, it will be used to prefix all HTTP endpoints served by Blackbox exporter. If omitted, relevant URL components will be derived automatically.").PlaceHolder("<url>").String()
 	routePrefix   = kingpin.Flag("web.route-prefix", "Prefix for the internal routes of web endpoints. Defaults to path of --web.external-url.").PlaceHolder("<path>").String()
 	toolkitFlags  = webflag.AddFlags(kingpin.CommandLine, ":9115")
+
+	moduleUnknownCounter = promauto.NewCounter(prometheus.CounterOpts{
+		Name: "blackbox_module_unknown_total",
+		Help: "Count of unknown modules requested by probes",
+	})
 )
 
 func init() {
@@ -178,7 +182,7 @@ func run() int {
 		sc.Lock()
 		conf := sc.C
 		sc.Unlock()
-		prober.Handler(w, r, conf, logger, rh, *timeoutOffset, nil)
+		prober.Handler(w, r, conf, logger, rh, *timeoutOffset, nil, moduleUnknownCounter)
 	})
 	http.HandleFunc(*routePrefix, func(w http.ResponseWriter, r *http.Request) {
 		w.Header().Set("Content-Type", "text/html")
diff --git a/prober/handler.go b/prober/handler.go
index 6658d3f..1caa2f9 100644
--- a/prober/handler.go
+++ b/prober/handler.go
@@ -27,7 +27,6 @@ import (
 	"github.com/go-kit/log/level"
 	"github.com/prometheus/blackbox_exporter/config"
 	"github.com/prometheus/client_golang/prometheus"
-	"github.com/prometheus/client_golang/prometheus/promauto"
 	"github.com/prometheus/client_golang/prometheus/promhttp"
 	"github.com/prometheus/common/expfmt"
 	"gopkg.in/yaml.v2"
@@ -41,14 +40,11 @@ var (
 		"dns":  ProbeDNS,
 		"grpc": ProbeGRPC,
 	}
-	moduleUnknownCounter = promauto.NewCounter(prometheus.CounterOpts{
-		Name: "blackbox_module_unknown_total",
-		Help: "Count of unknown modules requested by probes",
-	})
 )
 
 func Handler(w http.ResponseWriter, r *http.Request, c *config.Config, logger log.Logger,
-	rh *ResultHistory, timeoutOffset float64, params url.Values) {
+	rh *ResultHistory, timeoutOffset float64, params url.Values, moduleUnknownCounter prometheus.Counter) {
+
 	if params == nil {
 		params = r.URL.Query()
 	}
@@ -60,7 +56,9 @@ func Handler(w http.ResponseWriter, r *http.Request, c *config.Config, logger lo
 	if !ok {
 		http.Error(w, fmt.Sprintf("Unknown module %q", moduleName), http.StatusBadRequest)
 		level.Debug(logger).Log("msg", "Unknown module", "module", moduleName)
-		moduleUnknownCounter.Add(1)
+		if moduleUnknownCounter != nil {
+			moduleUnknownCounter.Add(1)
+		}
 		return
 	}
 


Test patch:
diff --git a/config/config_test.go b/config/config_test.go
index 65e3fe8..974e6f3 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -17,13 +17,12 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/prometheus/client_golang/prometheus"
 	yaml "gopkg.in/yaml.v3"
 )
 
 func TestLoadConfig(t *testing.T) {
-	sc := &SafeConfig{
-		C: &Config{},
-	}
+	sc := NewSafeConfig(prometheus.NewRegistry())
 
 	err := sc.ReloadConfig("testdata/blackbox-good.yml", nil)
 	if err != nil {
@@ -32,9 +31,7 @@ func TestLoadConfig(t *testing.T) {
 }
 
 func TestLoadBadConfigs(t *testing.T) {
-	sc := &SafeConfig{
-		C: &Config{},
-	}
+	sc := NewSafeConfig(prometheus.NewRegistry())
 	tests := []struct {
 		input string
 		want  string
@@ -115,9 +112,7 @@ func TestLoadBadConfigs(t *testing.T) {
 }
 
 func TestHideConfigSecrets(t *testing.T) {
-	sc := &SafeConfig{
-		C: &Config{},
-	}
+	sc := NewSafeConfig(prometheus.NewRegistry())
 
 	err := sc.ReloadConfig("testdata/blackbox-good.yml", nil)
 	if err != nil {
diff --git a/prober/handler_test.go b/prober/handler_test.go
index 40039d4..885f751 100644
--- a/prober/handler_test.go
+++ b/prober/handler_test.go
@@ -59,7 +59,7 @@ func TestPrometheusTimeoutHTTP(t *testing.T) {
 
 	rr := httptest.NewRecorder()
 	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		Handler(w, r, c, log.NewNopLogger(), &ResultHistory{}, 0.5, nil)
+		Handler(w, r, c, log.NewNopLogger(), &ResultHistory{}, 0.5, nil, nil)
 	})
 
 	handler.ServeHTTP(rr, req)
@@ -81,7 +81,7 @@ func TestPrometheusConfigSecretsHidden(t *testing.T) {
 	}
 	rr := httptest.NewRecorder()
 	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		Handler(w, r, c, log.NewNopLogger(), &ResultHistory{}, 0.5, nil)
+		Handler(w, r, c, log.NewNopLogger(), &ResultHistory{}, 0.5, nil, nil)
 	})
 	handler.ServeHTTP(rr, req)
 
@@ -177,7 +177,7 @@ func TestHostnameParam(t *testing.T) {
 	rr := httptest.NewRecorder()
 
 	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		Handler(w, r, c, log.NewNopLogger(), &ResultHistory{}, 0.5, nil)
+		Handler(w, r, c, log.NewNopLogger(), &ResultHistory{}, 0.5, nil, nil)
 	})
 
 	handler.ServeHTTP(rr, req)
@@ -195,7 +195,7 @@ func TestHostnameParam(t *testing.T) {
 	c.Modules["http_2xx"].HTTP.Headers["Host"] = hostname + ".something"
 
 	handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		Handler(w, r, c, log.NewNopLogger(), &ResultHistory{}, 0.5, nil)
+		Handler(w, r, c, log.NewNopLogger(), &ResultHistory{}, 0.5, nil, nil)
 	})
 
 	rr = httptest.NewRecorder()
@@ -242,7 +242,7 @@ func TestTCPHostnameParam(t *testing.T) {
 	rr := httptest.NewRecorder()
 
 	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		Handler(w, r, c, log.NewNopLogger(), &ResultHistory{}, 0.5, nil)
+		Handler(w, r, c, log.NewNopLogger(), &ResultHistory{}, 0.5, nil, nil)
 	})
 
 	handler.ServeHTTP(rr, req)