diff --git a/README.md b/README.md
index 69513d85c7..2ded6889ed 100644
--- a/README.md
+++ b/README.md
@@ -88,6 +88,19 @@ additional metrics!
| ---------- | ----------- | ----------- |
| kube_resource_quota | Gauge | `resourcequota`=<quota-name>
`namespace`=<namespace>
`resource`=<ResourceName>
`type`=<quota-type> |
+### ReplicaSet metrics
+
+| Metric name| Metric type | Labels/tags |
+| ---------- | ----------- | ----------- |
+| kube_replicaset_status_replicas | Gauge | `replicaset`=<replicaset-name>
`namespace`=<replicaset-namespace> |
+| kube_replicaset_status_replicas_available | Gauge | `replicaset`=<replicaset-name>
`namespace`=<replicaset-namespace> |
+| kube_replicaset_status_replicas_unavailable | Gauge | `replicaset`=<replicaset-name>
`namespace`=<replicaset-namespace> |
+| kube_replicaset_status_replicas_updated | Gauge | `replicaset`=<replicaset-name>
`namespace`=<replicaset-namespace> |
+| kube_replicaset_status_replicas_observed_generation | Gauge | `replicaset`=<replicaset-name>
`namespace`=<replicaset-namespace> |
+| kube_replicaset_spec_replicas | Gauge | `replicaset`=<replicaset-name>
`namespace`=<replicaset-namespace> |
+| kube_replicaset_spec_paused | Gauge | `replicaset`=<replicaset-name>
`namespace`=<replicaset-namespace> |
+| kube_replicaset_metadata_generation | Gauge | `replicaset`=<replicaset-name>
`namespace`=<replicaset-namespace> |
+
## kube-state-metrics vs. Heapster
[Heapster](https://github.com/kubernetes/heapster) is a project which fetches
diff --git a/main.go b/main.go
index 69fb58be76..f642357a4b 100644
--- a/main.go
+++ b/main.go
@@ -47,6 +47,7 @@ var (
"pods": struct{}{},
"nodes": struct{}{},
"resourcequotas": struct{}{},
+ "replicasets": struct{}{},
}
availableCollectors = map[string]func(registry prometheus.Registerer, kubeClient clientset.Interface){
"daemonsets": RegisterDaemonSetCollector,
@@ -54,6 +55,7 @@ var (
"pods": RegisterPodCollector,
"nodes": RegisterNodeCollector,
"resourcequotas": RegisterResourceQuotaCollector,
+ "replicasets": RegisterReplicaSetCollector,
}
)
diff --git a/replicaset.go b/replicaset.go
new file mode 100644
index 0000000000..8dde2fa8c5
--- /dev/null
+++ b/replicaset.go
@@ -0,0 +1,126 @@
+/*
+Copyright 2016 The Kubernetes Authors All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package main
+
+import (
+ "github.com/golang/glog"
+ "github.com/prometheus/client_golang/prometheus"
+ "golang.org/x/net/context"
+ "k8s.io/client-go/kubernetes"
+ "k8s.io/client-go/pkg/api"
+ "k8s.io/client-go/pkg/apis/extensions/v1beta1"
+ "k8s.io/client-go/tools/cache"
+)
+
+var (
+ descReplicaSetStatusReplicas = prometheus.NewDesc(
+ "kube_replicaset_status_replicas",
+ "The number of replicas per ReplicaSet.",
+ []string{"namespace", "replicaset"}, nil,
+ )
+ descReplicaSetStatusFullyLabeledReplicas = prometheus.NewDesc(
+ "kube_replicaset_status_fully_labeled_replicas",
+ "The number of fully labeled replicas per ReplicaSet.",
+ []string{"namespace", "replicaset"}, nil,
+ )
+ descReplicaSetStatusReadyReplicas = prometheus.NewDesc(
+ "kube_replicaset_status_ready_replicas",
+ "The number of ready replicas per ReplicaSet.",
+ []string{"namespace", "replicaset"}, nil,
+ )
+ descReplicaSetStatusObservedGeneration = prometheus.NewDesc(
+ "kube_replicaset_status_observed_generation",
+ "The generation observed by the ReplicaSet controller.",
+ []string{"namespace", "replicaset"}, nil,
+ )
+ descReplicaSetSpecReplicas = prometheus.NewDesc(
+ "kube_replicaset_spec_replicas",
+ "Number of desired pods for a ReplicaSet.",
+ []string{"namespace", "replicaset"}, nil,
+ )
+ descReplicaSetMetadataGeneration = prometheus.NewDesc(
+ "kube_replicaset_metadata_generation",
+ "Sequence number representing a specific generation of the desired state.",
+ []string{"namespace", "replicaset"}, nil,
+ )
+)
+
+type ReplicaSetLister func() ([]v1beta1.ReplicaSet, error)
+
+func (l ReplicaSetLister) List() ([]v1beta1.ReplicaSet, error) {
+ return l()
+}
+
+func RegisterReplicaSetCollector(registry prometheus.Registerer, kubeClient kubernetes.Interface) {
+ client := kubeClient.Extensions().RESTClient()
+ rslw := cache.NewListWatchFromClient(client, "replicasets", api.NamespaceAll, nil)
+ rsinf := cache.NewSharedInformer(rslw, &v1beta1.ReplicaSet{}, resyncPeriod)
+
+ replicaSetLister := ReplicaSetLister(func() (replicasets []v1beta1.ReplicaSet, err error) {
+ for _, c := range rsinf.GetStore().List() {
+ replicasets = append(replicasets, *(c.(*v1beta1.ReplicaSet)))
+ }
+ return replicasets, nil
+ })
+
+ registry.MustRegister(&replicasetCollector{store: replicaSetLister})
+ go rsinf.Run(context.Background().Done())
+}
+
+type replicasetStore interface {
+ List() (replicasets []v1beta1.ReplicaSet, err error)
+}
+
+// replicasetCollector collects metrics about all replicasets in the cluster.
+type replicasetCollector struct {
+ store replicasetStore
+}
+
+// Describe implements the prometheus.Collector interface.
+func (dc *replicasetCollector) Describe(ch chan<- *prometheus.Desc) {
+ ch <- descReplicaSetStatusReplicas
+ ch <- descReplicaSetStatusFullyLabeledReplicas
+ ch <- descReplicaSetStatusReadyReplicas
+ ch <- descReplicaSetStatusObservedGeneration
+ ch <- descReplicaSetSpecReplicas
+ ch <- descReplicaSetMetadataGeneration
+}
+
+// Collect implements the prometheus.Collector interface.
+func (dc *replicasetCollector) Collect(ch chan<- prometheus.Metric) {
+ dpls, err := dc.store.List()
+ if err != nil {
+ glog.Errorf("listing replicasets failed: %s", err)
+ return
+ }
+ for _, d := range dpls {
+ dc.collectReplicaSet(ch, d)
+ }
+}
+
+func (dc *replicasetCollector) collectReplicaSet(ch chan<- prometheus.Metric, d v1beta1.ReplicaSet) {
+ addGauge := func(desc *prometheus.Desc, v float64, lv ...string) {
+ lv = append([]string{d.Namespace, d.Name}, lv...)
+ ch <- prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, v, lv...)
+ }
+ addGauge(descReplicaSetStatusReplicas, float64(d.Status.Replicas))
+ addGauge(descReplicaSetStatusFullyLabeledReplicas, float64(d.Status.FullyLabeledReplicas))
+ addGauge(descReplicaSetStatusReadyReplicas, float64(d.Status.ReadyReplicas))
+ addGauge(descReplicaSetStatusObservedGeneration, float64(d.Status.ObservedGeneration))
+ addGauge(descReplicaSetSpecReplicas, float64(*d.Spec.Replicas))
+ addGauge(descReplicaSetMetadataGeneration, float64(d.ObjectMeta.Generation))
+}
diff --git a/replicaset_test.go b/replicaset_test.go
new file mode 100644
index 0000000000..c625037848
--- /dev/null
+++ b/replicaset_test.go
@@ -0,0 +1,120 @@
+/*
+Copyright 2016 The Kubernetes Authors All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package main
+
+import (
+ "testing"
+
+ "k8s.io/client-go/pkg/api/v1"
+ "k8s.io/client-go/pkg/apis/extensions/v1beta1"
+)
+
+var (
+ rs1Replicas int32 = 5
+ rs2Replicas int32 = 0
+)
+
+type mockReplicaSetStore struct {
+ f func() ([]v1beta1.ReplicaSet, error)
+}
+
+func (rs mockReplicaSetStore) List() (replicasets []v1beta1.ReplicaSet, err error) {
+ return rs.f()
+}
+
+func TestReplicaSetCollector(t *testing.T) {
+ // Fixed metadata on type and help text. We prepend this to every expected
+ // output so we only have to modify a single place when doing adjustments.
+ const metadata = `
+ # HELP kube_replicaset_metadata_generation Sequence number representing a specific generation of the desired state.
+ # TYPE kube_replicaset_metadata_generation gauge
+ # HELP kube_replicaset_status_replicas The number of replicas per ReplicaSet.
+ # TYPE kube_replicaset_status_replicas gauge
+ # HELP kube_replicaset_status_fully_labeled_replicas The number of fully labeled replicas per ReplicaSet.
+ # TYPE kube_replicaset_status_fully_labeled_replicas gauge
+ # HELP kube_replicaset_status_ready_replicas The number of ready replicas per ReplicaSet.
+ # TYPE kube_replicaset_status_ready_replicas gauge
+ # HELP kube_replicaset_status_observed_generation The generation observed by the ReplicaSet controller.
+ # TYPE kube_replicaset_status_observed_generation gauge
+ # HELP kube_replicaset_spec_replicas Number of desired pods for a ReplicaSet.
+ # TYPE kube_replicaset_spec_replicas gauge
+ `
+ cases := []struct {
+ rss []v1beta1.ReplicaSet
+ want string
+ }{
+ {
+ rss: []v1beta1.ReplicaSet{
+ {
+ ObjectMeta: v1.ObjectMeta{
+ Name: "rs1",
+ Namespace: "ns1",
+ Generation: 21,
+ },
+ Status: v1beta1.ReplicaSetStatus{
+ Replicas: 5,
+ FullyLabeledReplicas: 10,
+ ReadyReplicas: 5,
+ ObservedGeneration: 1,
+ },
+ Spec: v1beta1.ReplicaSetSpec{
+ Replicas: &rs1Replicas,
+ },
+ }, {
+ ObjectMeta: v1.ObjectMeta{
+ Name: "rs2",
+ Namespace: "ns2",
+ Generation: 14,
+ },
+ Status: v1beta1.ReplicaSetStatus{
+ Replicas: 0,
+ FullyLabeledReplicas: 5,
+ ReadyReplicas: 0,
+ ObservedGeneration: 5,
+ },
+ Spec: v1beta1.ReplicaSetSpec{
+ Replicas: &rs2Replicas,
+ },
+ },
+ },
+ want: metadata + `
+ kube_replicaset_metadata_generation{namespace="ns1",replicaset="rs1"} 21
+ kube_replicaset_metadata_generation{namespace="ns2",replicaset="rs2"} 14
+ kube_replicaset_status_replicas{namespace="ns1",replicaset="rs1"} 5
+ kube_replicaset_status_replicas{namespace="ns2",replicaset="rs2"} 0
+ kube_replicaset_status_observed_generation{namespace="ns1",replicaset="rs1"} 1
+ kube_replicaset_status_observed_generation{namespace="ns2",replicaset="rs2"} 5
+ kube_replicaset_status_fully_labeled_replicas{namespace="ns1",replicaset="rs1"} 10
+ kube_replicaset_status_fully_labeled_replicas{namespace="ns2",replicaset="rs2"} 5
+ kube_replicaset_status_ready_replicas{namespace="ns1",replicaset="rs1"} 5
+ kube_replicaset_status_ready_replicas{namespace="ns2",replicaset="rs2"} 0
+ kube_replicaset_spec_replicas{namespace="ns1",replicaset="rs1"} 5
+ kube_replicaset_spec_replicas{namespace="ns2",replicaset="rs2"} 0
+ `,
+ },
+ }
+ for _, c := range cases {
+ dc := &replicasetCollector{
+ store: mockReplicaSetStore{
+ f: func() ([]v1beta1.ReplicaSet, error) { return c.rss, nil },
+ },
+ }
+ if err := gatherAndCompare(dc, c.want, nil); err != nil {
+ t.Errorf("unexpected collecting result:\n%s", err)
+ }
+ }
+}