diff --git a/pkg/featuregate/feature_gate.go b/pkg/featuregate/feature_gate.go index c899616d92a6..ee4ed113d3dc 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..70479a05dbce 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,43 @@ 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{ + 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 test_metric Whether or not a feature is enabled. 1 is enabled, 0 is not. + # TYPE test_metric gauge + test_metric{name="AllAlpha",stage="ALPHA"} 0 + test_metric{name="AllBeta",stage="BETA"} 0 + test_metric{name="TestAlpha",stage="ALPHA"} 1 + test_metric{name="TestBeta",stage="BETA"} 0 + test_metric{name="TestGA",stage=""} 1 + ` + err = testutil.GatherAndCompare(prometheus.DefaultGatherer, strings.NewReader(expected), "test_metric") + require.NoErrorf(t, err, "unexpected metric collection result: \n%s", err) +} diff --git a/pkg/go.mod b/pkg/go.mod index 515c054ff1ea..d4f9ddb4b18c 100644 --- a/pkg/go.mod +++ b/pkg/go.mod @@ -16,10 +16,18 @@ require ( ) require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_golang v1.21.0 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.62.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect go.opentelemetry.io/otel v1.34.0 // indirect go.opentelemetry.io/otel/sdk v1.34.0 // indirect go.uber.org/multierr v1.11.0 // indirect diff --git a/pkg/go.sum b/pkg/go.sum index 63cee0e19954..58bbc38a53c8 100644 --- a/pkg/go.sum +++ b/pkg/go.sum @@ -1,3 +1,7 @@ +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= @@ -24,8 +28,20 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.21.0 h1:DIsaGmiaBkSangBgMtWdNfxbMNdku5IK6iNhrEqWvdA= +github.com/prometheus/client_golang v1.21.0/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= +github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= diff --git a/server/etcdserver/metrics.go b/server/etcdserver/metrics.go index 5f3c2f51368f..2ece0c8f7725 100644 --- a/server/etcdserver/metrics.go +++ b/server/etcdserver/metrics.go @@ -140,6 +140,13 @@ var ( }, []string{"server_id"}, ) + etcdServerFeatureEnabled = 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(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)