diff --git a/README.md b/README.md
index 1186ded4..336e65b6 100644
--- a/README.md
+++ b/README.md
@@ -105,6 +105,18 @@ additional metrics!
| kube_replicaset_spec_paused | Gauge | `replicaset`=<replicaset-name>
`namespace`=<replicaset-namespace> |
| kube_replicaset_metadata_generation | Gauge | `replicaset`=<replicaset-name>
`namespace`=<replicaset-namespace> |
+### ReplicationController metrics
+
+| Metric name| Metric type | Labels/tags |
+| ---------- | ----------- | ----------- |
+| kube_replicationcontroller_status_replicas | Gauge | `replicationcontroller`=<replicationcontroller-name>
`namespace`=<replicationcontroller-namespace> |
+| kube_replicationcontroller_status_fully_labeled_replicas | Gauge | `replicationcontroller`=<replicationcontroller-name>
`namespace`=<replicationcontroller-namespace> |
+| kube_replicationcontroller_status_ready_replicas | Gauge | `replicationcontroller`=<replicationcontroller-name>
`namespace`=<replicationcontroller-namespace> |
+| kube_replicationcontroller_status_available_replicas | Gauge | `replicationcontroller`=<replicationcontroller-name>
`namespace`=<replicationcontroller-namespace> |
+| kube_replicationcontroller_status_replicas_observed_generation | Gauge | `replicationcontroller`=<replicationcontroller-name>
`namespace`=<replicationcontroller-namespace> |
+| kube_replicationcontroller_spec_replicas | Gauge | `replicationcontroller`=<replicationcontroller-name>
`namespace`=<replicationcontroller-namespace> |
+| kube_replicationcontroller_metadata_generation | Gauge | `replicationcontroller`=<replicationcontroller-name>
`namespace`=<replicationcontroller-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 f642357a..501cb47b 100644
--- a/main.go
+++ b/main.go
@@ -42,20 +42,22 @@ const (
var (
defaultCollectors = collectorSet{
- "daemonsets": struct{}{},
- "deployments": struct{}{},
- "pods": struct{}{},
- "nodes": struct{}{},
- "resourcequotas": struct{}{},
- "replicasets": struct{}{},
+ "daemonsets": struct{}{},
+ "deployments": struct{}{},
+ "pods": struct{}{},
+ "nodes": struct{}{},
+ "resourcequotas": struct{}{},
+ "replicasets": struct{}{},
+ "replicationcontrollers": struct{}{},
}
availableCollectors = map[string]func(registry prometheus.Registerer, kubeClient clientset.Interface){
- "daemonsets": RegisterDaemonSetCollector,
- "deployments": RegisterDeploymentCollector,
- "pods": RegisterPodCollector,
- "nodes": RegisterNodeCollector,
- "resourcequotas": RegisterResourceQuotaCollector,
- "replicasets": RegisterReplicaSetCollector,
+ "daemonsets": RegisterDaemonSetCollector,
+ "deployments": RegisterDeploymentCollector,
+ "pods": RegisterPodCollector,
+ "nodes": RegisterNodeCollector,
+ "resourcequotas": RegisterResourceQuotaCollector,
+ "replicasets": RegisterReplicaSetCollector,
+ "replicationcontrollers": RegisterReplicationControllerCollector,
}
)
diff --git a/replicationcontroller.go b/replicationcontroller.go
new file mode 100644
index 00000000..35d1b219
--- /dev/null
+++ b/replicationcontroller.go
@@ -0,0 +1,133 @@
+/*
+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 (
+ "context"
+
+ "github.com/golang/glog"
+ "github.com/prometheus/client_golang/prometheus"
+ "k8s.io/client-go/kubernetes"
+ "k8s.io/client-go/pkg/api"
+ "k8s.io/client-go/pkg/api/v1"
+ "k8s.io/client-go/tools/cache"
+)
+
+var (
+ descReplicationControllerStatusReplicas = prometheus.NewDesc(
+ "kube_replicationcontroller_status_replicas",
+ "The number of replicas per ReplicationController.",
+ []string{"namespace", "replicationcontroller"}, nil,
+ )
+ descReplicationControllerStatusFullyLabeledReplicas = prometheus.NewDesc(
+ "kube_replicationcontroller_status_fully_labeled_replicas",
+ "The number of fully labeled replicas per ReplicationController.",
+ []string{"namespace", "replicationcontroller"}, nil,
+ )
+ descReplicationControllerStatusReadyReplicas = prometheus.NewDesc(
+ "kube_replicationcontroller_status_ready_replicas",
+ "The number of ready replicas per ReplicationController.",
+ []string{"namespace", "replicationcontroller"}, nil,
+ )
+ descReplicationControllerStatusAvailableReplicas = prometheus.NewDesc(
+ "kube_replicationcontroller_status_available_replicas",
+ "The number of available replicas per ReplicationController.",
+ []string{"namespace", "replicationcontroller"}, nil,
+ )
+ descReplicationControllerStatusObservedGeneration = prometheus.NewDesc(
+ "kube_replicationcontroller_status_observed_generation",
+ "The generation observed by the ReplicationController controller.",
+ []string{"namespace", "replicationcontroller"}, nil,
+ )
+ descReplicationControllerSpecReplicas = prometheus.NewDesc(
+ "kube_replicationcontroller_spec_replicas",
+ "Number of desired pods for a ReplicationController.",
+ []string{"namespace", "replicationcontroller"}, nil,
+ )
+ descReplicationControllerMetadataGeneration = prometheus.NewDesc(
+ "kube_replicationcontroller_metadata_generation",
+ "Sequence number representing a specific generation of the desired state.",
+ []string{"namespace", "replicationcontroller"}, nil,
+ )
+)
+
+type ReplicationControllerLister func() ([]v1.ReplicationController, error)
+
+func (l ReplicationControllerLister) List() ([]v1.ReplicationController, error) {
+ return l()
+}
+
+func RegisterReplicationControllerCollector(registry prometheus.Registerer, kubeClient kubernetes.Interface) {
+ client := kubeClient.Extensions().RESTClient()
+ rclw := cache.NewListWatchFromClient(client, "replicationcontrollers", api.NamespaceAll, nil)
+ rcinf := cache.NewSharedInformer(rclw, &v1.ReplicationController{}, resyncPeriod)
+
+ replicationControllerLister := ReplicationControllerLister(func() (rcs []v1.ReplicationController, err error) {
+ for _, c := range rcinf.GetStore().List() {
+ rcs = append(rcs, *(c.(*v1.ReplicationController)))
+ }
+ return rcs, nil
+ })
+
+ registry.MustRegister(&replicationcontrollerCollector{store: replicationControllerLister})
+ go rcinf.Run(context.Background().Done())
+}
+
+type replicationcontrollerStore interface {
+ List() (replicationcontrollers []v1.ReplicationController, err error)
+}
+
+type replicationcontrollerCollector struct {
+ store replicationcontrollerStore
+}
+
+// Describe implements the prometheus.Collector interface.
+func (dc *replicationcontrollerCollector) Describe(ch chan<- *prometheus.Desc) {
+ ch <- descReplicationControllerStatusReplicas
+ ch <- descReplicationControllerStatusFullyLabeledReplicas
+ ch <- descReplicationControllerStatusReadyReplicas
+ ch <- descReplicationControllerStatusAvailableReplicas
+ ch <- descReplicationControllerStatusObservedGeneration
+ ch <- descReplicationControllerSpecReplicas
+ ch <- descReplicationControllerMetadataGeneration
+}
+
+// Collect implements the prometheus.Collector interface.
+func (dc *replicationcontrollerCollector) Collect(ch chan<- prometheus.Metric) {
+ dpls, err := dc.store.List()
+ if err != nil {
+ glog.Errorf("listing replicationcontrollers failed: %s", err)
+ return
+ }
+ for _, d := range dpls {
+ dc.collectReplicationController(ch, d)
+ }
+}
+
+func (dc *replicationcontrollerCollector) collectReplicationController(ch chan<- prometheus.Metric, d v1.ReplicationController) {
+ 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(descReplicationControllerStatusReplicas, float64(d.Status.Replicas))
+ addGauge(descReplicationControllerStatusFullyLabeledReplicas, float64(d.Status.FullyLabeledReplicas))
+ addGauge(descReplicationControllerStatusReadyReplicas, float64(d.Status.ReadyReplicas))
+ addGauge(descReplicationControllerStatusAvailableReplicas, float64(d.Status.AvailableReplicas))
+ addGauge(descReplicationControllerStatusObservedGeneration, float64(d.Status.ObservedGeneration))
+ addGauge(descReplicationControllerSpecReplicas, float64(*d.Spec.Replicas))
+ addGauge(descReplicationControllerMetadataGeneration, float64(d.ObjectMeta.Generation))
+}
diff --git a/replicationcontroller_test.go b/replicationcontroller_test.go
new file mode 100644
index 00000000..9c149c5d
--- /dev/null
+++ b/replicationcontroller_test.go
@@ -0,0 +1,125 @@
+/*
+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"
+)
+
+var (
+ rc1Replicas int32 = 5
+ rc2Replicas int32 = 0
+)
+
+type mockReplicationControllerStore struct {
+ f func() ([]v1.ReplicationController, error)
+}
+
+func (rs mockReplicationControllerStore) List() (replicationcontrollers []v1.ReplicationController, err error) {
+ return rs.f()
+}
+
+func TestReplicationControllerCollector(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_replicationcontroller_metadata_generation Sequence number representing a specific generation of the desired state.
+ # TYPE kube_replicationcontroller_metadata_generation gauge
+ # HELP kube_replicationcontroller_status_replicas The number of replicas per ReplicationController.
+ # TYPE kube_replicationcontroller_status_replicas gauge
+ # HELP kube_replicationcontroller_status_fully_labeled_replicas The number of fully labeled replicas per ReplicationController.
+ # TYPE kube_replicationcontroller_status_fully_labeled_replicas gauge
+ # HELP kube_replicationcontroller_status_available_replicas The number of available replicas per ReplicationController.
+ # TYPE kube_replicationcontroller_status_available_replicas gauge
+ # HELP kube_replicationcontroller_status_ready_replicas The number of ready replicas per ReplicationController.
+ # TYPE kube_replicationcontroller_status_ready_replicas gauge
+ # HELP kube_replicationcontroller_status_observed_generation The generation observed by the ReplicationController controller.
+ # TYPE kube_replicationcontroller_status_observed_generation gauge
+ # HELP kube_replicationcontroller_spec_replicas Number of desired pods for a ReplicationController.
+ # TYPE kube_replicationcontroller_spec_replicas gauge
+ `
+ cases := []struct {
+ rss []v1.ReplicationController
+ want string
+ }{
+ {
+ rss: []v1.ReplicationController{
+ {
+ ObjectMeta: v1.ObjectMeta{
+ Name: "rc1",
+ Namespace: "ns1",
+ Generation: 21,
+ },
+ Status: v1.ReplicationControllerStatus{
+ Replicas: 5,
+ FullyLabeledReplicas: 10,
+ ReadyReplicas: 5,
+ AvailableReplicas: 3,
+ ObservedGeneration: 1,
+ },
+ Spec: v1.ReplicationControllerSpec{
+ Replicas: &rc1Replicas,
+ },
+ }, {
+ ObjectMeta: v1.ObjectMeta{
+ Name: "rc2",
+ Namespace: "ns2",
+ Generation: 14,
+ },
+ Status: v1.ReplicationControllerStatus{
+ Replicas: 0,
+ FullyLabeledReplicas: 5,
+ ReadyReplicas: 0,
+ AvailableReplicas: 0,
+ ObservedGeneration: 5,
+ },
+ Spec: v1.ReplicationControllerSpec{
+ Replicas: &rc2Replicas,
+ },
+ },
+ },
+ want: metadata + `
+ kube_replicationcontroller_metadata_generation{namespace="ns1",replicationcontroller="rc1"} 21
+ kube_replicationcontroller_metadata_generation{namespace="ns2",replicationcontroller="rc2"} 14
+ kube_replicationcontroller_status_replicas{namespace="ns1",replicationcontroller="rc1"} 5
+ kube_replicationcontroller_status_replicas{namespace="ns2",replicationcontroller="rc2"} 0
+ kube_replicationcontroller_status_observed_generation{namespace="ns1",replicationcontroller="rc1"} 1
+ kube_replicationcontroller_status_observed_generation{namespace="ns2",replicationcontroller="rc2"} 5
+ kube_replicationcontroller_status_fully_labeled_replicas{namespace="ns1",replicationcontroller="rc1"} 10
+ kube_replicationcontroller_status_fully_labeled_replicas{namespace="ns2",replicationcontroller="rc2"} 5
+ kube_replicationcontroller_status_ready_replicas{namespace="ns1",replicationcontroller="rc1"} 5
+ kube_replicationcontroller_status_ready_replicas{namespace="ns2",replicationcontroller="rc2"} 0
+ kube_replicationcontroller_status_available_replicas{namespace="ns1",replicationcontroller="rc1"} 3
+ kube_replicationcontroller_status_available_replicas{namespace="ns2",replicationcontroller="rc2"} 0
+ kube_replicationcontroller_spec_replicas{namespace="ns1",replicationcontroller="rc1"} 5
+ kube_replicationcontroller_spec_replicas{namespace="ns2",replicationcontroller="rc2"} 0
+ `,
+ },
+ }
+ for _, c := range cases {
+ dc := &replicationcontrollerCollector{
+ store: mockReplicationControllerStore{
+ f: func() ([]v1.ReplicationController, error) { return c.rss, nil },
+ },
+ }
+ if err := gatherAndCompare(dc, c.want, nil); err != nil {
+ t.Errorf("unexpected collecting result:\n%s", err)
+ }
+ }
+}