diff --git a/pkg/featuregate/feature_gate.go b/pkg/featuregate/feature_gate.go index c899616d92a..343668fc304 100644 --- a/pkg/featuregate/feature_gate.go +++ b/pkg/featuregate/feature_gate.go @@ -112,8 +112,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,10 +361,6 @@ func (f *featureGate) AddFlag(fs *flag.FlagSet, flagName string) { "Options are:\n"+strings.Join(known, "\n")) } -func (f *featureGate) AddMetrics() { - // TODO(henrybear327): implement this. -} - // KnownFeatures returns a slice of strings describing the FeatureGate's known features. // Deprecated and GA features are hidden from the list. func (f *featureGate) KnownFeatures() []string { diff --git a/server/etcdserver/metrics.go b/server/etcdserver/metrics.go index 5f3c2f51368..7176d30adbc 100644 --- a/server/etcdserver/metrics.go +++ b/server/etcdserver/metrics.go @@ -140,6 +140,13 @@ var ( }, []string{"server_id"}, ) + serverFeatureEnabled = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + 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 +177,7 @@ func init() { prometheus.MustRegister(currentVersion) prometheus.MustRegister(currentGoVersion) prometheus.MustRegister(serverID) + prometheus.MustRegister(serverFeatureEnabled) prometheus.MustRegister(learnerPromoteSucceed) prometheus.MustRegister(learnerPromoteFailed) prometheus.MustRegister(fdUsed) diff --git a/server/etcdserver/server.go b/server/etcdserver/server.go index 2c45553bc83..008d23b5530 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(), } + + addFeatureGateMetrics(cfg.ServerFeatureGate, serverFeatureEnabled) serverID.With(prometheus.Labels{"server_id": b.cluster.nodeID.String()}).Set(1) srv.cluster.SetVersionChangedNotifier(srv.clusterVersionChanged) @@ -2519,3 +2521,15 @@ func (s *EtcdServer) getTxPostLockInsideApplyHook() func() { func (s *EtcdServer) CorruptionChecker() CorruptionChecker { return s.corruptionChecker } + +func addFeatureGateMetrics(fg featuregate.FeatureGate, guageVec *prometheus.GaugeVec) { + for feature, featureSpec := range fg.(featuregate.MutableFeatureGate).GetAll() { + var metricVal float64 + if fg.Enabled(feature) { + metricVal = 1 + } else { + metricVal = 0 + } + guageVec.With(prometheus.Labels{"name": string(feature), "stage": string(featureSpec.PreRelease)}).Set(metricVal) + } +} diff --git a/server/etcdserver/server_test.go b/server/etcdserver/server_test.go index d3f379837f4..c7843ebcfb2 100644 --- a/server/etcdserver/server_test.go +++ b/server/etcdserver/server_test.go @@ -24,12 +24,15 @@ import ( "os" "path/filepath" "reflect" + "strings" "sync" "testing" "time" "github.com/coreos/go-semver/semver" "github.com/golang/protobuf/proto" + "github.com/prometheus/client_golang/prometheus" + ptestutil "github.com/prometheus/client_golang/prometheus/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" @@ -42,6 +45,7 @@ import ( "go.etcd.io/etcd/client/pkg/v3/testutil" "go.etcd.io/etcd/client/pkg/v3/types" "go.etcd.io/etcd/client/pkg/v3/verify" + "go.etcd.io/etcd/pkg/v3/featuregate" "go.etcd.io/etcd/pkg/v3/idutil" "go.etcd.io/etcd/pkg/v3/notify" "go.etcd.io/etcd/pkg/v3/pbutil" @@ -1683,3 +1687,30 @@ func TestIsActive(t *testing.T) { require.Equal(t, tc.expectActive, s.isActive()) } } + +func TestAddFeatureGateMetrics(t *testing.T) { + const testAlphaGate featuregate.Feature = "TestAlpha" + const testBetaGate featuregate.Feature = "TestBeta" + const testGAGate featuregate.Feature = "TestGA" + + featuremap := map[featuregate.Feature]featuregate.FeatureSpec{ + testGAGate: {Default: true, PreRelease: featuregate.GA}, + testAlphaGate: {Default: true, PreRelease: featuregate.Alpha}, + testBetaGate: {Default: false, PreRelease: featuregate.Beta}, + } + fg := featuregate.New("test", zaptest.NewLogger(t)) + fg.Add(featuremap) + + addFeatureGateMetrics(fg, serverFeatureEnabled) + + expected := `# HELP etcd_server_feature_enabled Whether or not a feature is enabled. 1 is enabled, 0 is not. + # TYPE etcd_server_feature_enabled gauge + etcd_server_feature_enabled{name="AllAlpha",stage="ALPHA"} 0 + etcd_server_feature_enabled{name="AllBeta",stage="BETA"} 0 + etcd_server_feature_enabled{name="TestAlpha",stage="ALPHA"} 1 + etcd_server_feature_enabled{name="TestBeta",stage="BETA"} 0 + etcd_server_feature_enabled{name="TestGA",stage=""} 1 + ` + err := ptestutil.GatherAndCompare(prometheus.DefaultGatherer, strings.NewReader(expected), "etcd_server_feature_enabled") + require.NoErrorf(t, err, "unexpected metric collection result: \n%s", err) +}