diff --git a/pkg/featuregate/feature_gate.go b/pkg/featuregate/feature_gate.go index c899616d92a6..9c2bb24c6f77 100644 --- a/pkg/featuregate/feature_gate.go +++ b/pkg/featuregate/feature_gate.go @@ -25,6 +25,7 @@ import ( "sync" "sync/atomic" + "github.com/prometheus/client_golang/prometheus" "github.com/spf13/pflag" "go.uber.org/zap" ) @@ -94,6 +95,8 @@ type FeatureGate interface { DeepCopy() MutableFeatureGate // String returns a string containing all enabled feature gates, formatted as "key1=value1,key2=value2,...". String() string + // AddMetrics adds feature enablement metrics + AddMetrics(gaugeVec *prometheus.GaugeVec) } // MutableFeatureGate parses and stores flag gates for known features from @@ -112,8 +115,6 @@ type MutableFeatureGate interface { Add(features map[Feature]FeatureSpec) error // GetAll returns a copy of the map of known feature names to feature specs. GetAll() map[Feature]FeatureSpec - // AddMetrics adds feature enablement metrics - AddMetrics() // OverrideDefault sets a local override for the registered default value of a named // feature. If the feature has not been previously registered (e.g. by a call to Add), has a // locked default, or if the gate has already registered itself with a FlagSet, a non-nil @@ -363,8 +364,16 @@ func (f *featureGate) AddFlag(fs *flag.FlagSet, flagName string) { "Options are:\n"+strings.Join(known, "\n")) } -func (f *featureGate) AddMetrics() { - // TODO(henrybear327): implement this. +func (f *featureGate) AddMetrics(gaugeVec *prometheus.GaugeVec) { + for Feature, FeatureSpec := range f.GetAll() { + var metricVal float64 + if f.Enabled(Feature) { + metricVal = 1 + } else { + metricVal = 0 + } + gaugeVec.With(prometheus.Labels{"name": string(Feature), "stage": string(FeatureSpec.PreRelease)}).Set(metricVal) + } } // KnownFeatures returns a slice of strings describing the FeatureGate's known features. diff --git a/pkg/featuregate/feature_gate_test.go b/pkg/featuregate/feature_gate_test.go index 5dc5a86537d7..623cbe67640c 100644 --- a/pkg/featuregate/feature_gate_test.go +++ b/pkg/featuregate/feature_gate_test.go @@ -20,6 +20,8 @@ import ( "strings" "testing" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" @@ -555,3 +557,45 @@ func TestFeatureGateOverrideDefault(t *testing.T) { assert.Errorf(t, err, "expected a non-nil error to be returned") }) } + +func TestAddMetric(t *testing.T) { + + etcdServerFeatureEnabled := prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Namespace: "etcd", + Subsystem: "server", + Name: "test_metric", + Help: "Whether or not a feature is enabled. 1 is enabled, 0 is not.", + }, + []string{"name", "stage"}, + ) + prometheus.MustRegister(etcdServerFeatureEnabled) + + const testAlphaGate Feature = "TestAlpha" + const testBetaGate Feature = "TestBeta" + const testGAGate Feature = "TestGA" + + featuremap := map[Feature]FeatureSpec{ + testGAGate: {Default: true, PreRelease: GA}, + testAlphaGate: {Default: false, PreRelease: Alpha}, + testBetaGate: {Default: false, PreRelease: Beta}, + } + + f := New("test", zaptest.NewLogger(t)) + f.Add(featuremap) + err := f.SetFromMap(map[string]bool{"TestAlpha": true}) + require.NoError(t, err) + + f.AddMetrics(etcdServerFeatureEnabled) + + expected := `# HELP etcd_server_test_metric Whether or not a feature is enabled. 1 is enabled, 0 is not. + # TYPE etcd_server_test_metric gauge + etcd_server_test_metric{name="AllAlpha",stage="ALPHA"} 0 + etcd_server_test_metric{name="AllBeta",stage="BETA"} 0 + etcd_server_test_metric{name="TestAlpha",stage="ALPHA"} 1 + etcd_server_test_metric{name="TestBeta",stage="BETA"} 0 + etcd_server_test_metric{name="TestGA",stage=""} 1 + ` + err = testutil.GatherAndCompare(prometheus.DefaultGatherer, strings.NewReader(expected), "etcd_server_test_metric") + require.NoErrorf(t, err, "unexpected metric collection result: \n%s", err) +} diff --git a/server/etcdserver/metrics.go b/server/etcdserver/metrics.go index 5f3c2f51368f..1876f6d0b022 100644 --- a/server/etcdserver/metrics.go +++ b/server/etcdserver/metrics.go @@ -140,6 +140,15 @@ var ( }, []string{"server_id"}, ) + etcdServerFeatureEnabled = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Namespace: "etcd", + Subsystem: "server", + Name: "etcd_server_feature_enabled", + Help: "Whether or not a feature is enabled. 1 is enabled, 0 is not.", + }, + []string{"name", "stage"}, + ) fdUsed = prometheus.NewGauge(prometheus.GaugeOpts{ Namespace: "os", Subsystem: "fd", @@ -170,6 +179,7 @@ func init() { prometheus.MustRegister(currentVersion) prometheus.MustRegister(currentGoVersion) prometheus.MustRegister(serverID) + prometheus.MustRegister(etcdServerFeatureEnabled) prometheus.MustRegister(learnerPromoteSucceed) prometheus.MustRegister(learnerPromoteFailed) prometheus.MustRegister(fdUsed) diff --git a/server/etcdserver/server.go b/server/etcdserver/server.go index 2c45553bc833..b8238b99efc2 100644 --- a/server/etcdserver/server.go +++ b/server/etcdserver/server.go @@ -342,6 +342,8 @@ func NewServer(cfg config.ServerConfig) (srv *EtcdServer, err error) { firstCommitInTerm: notify.NewNotifier(), clusterVersionChanged: notify.NewNotifier(), } + + cfg.ServerFeatureGate.(featuregate.MutableFeatureGate).AddMetrics(etcdServerFeatureEnabled) serverID.With(prometheus.Labels{"server_id": b.cluster.nodeID.String()}).Set(1) srv.cluster.SetVersionChangedNotifier(srv.clusterVersionChanged)