From edd182ece095b8a9dcc7bbf5b92a8445c21167d6 Mon Sep 17 00:00:00 2001 From: Frederic Branczyk Date: Thu, 2 Feb 2017 13:54:47 +0100 Subject: [PATCH] add replicaset metrics --- README.md | 13 +++++ main.go | 2 + replicaset.go | 126 +++++++++++++++++++++++++++++++++++++++++++++ replicaset_test.go | 120 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 261 insertions(+) create mode 100644 replicaset.go create mode 100644 replicaset_test.go 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) + } + } +}