diff --git a/main_test.go b/main_test.go index b4a90cb0..3d620b03 100644 --- a/main_test.go +++ b/main_test.go @@ -179,6 +179,8 @@ func TestFullScrapeCycle(t *testing.T) { # HELP kube_job_failed The job has failed its execution. # HELP kube_job_status_start_time StartTime represents time when the job was acknowledged by the Job Manager. # HELP kube_job_status_completion_time CompletionTime represents time when the job was completed. +# HELP kube_limitrange Information about limit range. +# HELP kube_limitrange_created Unix creation timestamp # HELP kube_pod_info Information about pod. # HELP kube_pod_start_time Start time in unix timestamp for a pod. # HELP kube_pod_completion_time Completion time in unix timestamp for a pod. diff --git a/pkg/collectors/builder.go b/pkg/collectors/builder.go index 819ad003..2a0298b0 100644 --- a/pkg/collectors/builder.go +++ b/pkg/collectors/builder.go @@ -123,6 +123,7 @@ var availableCollectors = map[string]func(f *Builder) *Collector{ "daemonsets": func(b *Builder) *Collector { return b.buildDaemonSetCollector() }, "deployments": func(b *Builder) *Collector { return b.buildDeploymentCollector() }, "jobs": func(b *Builder) *Collector { return b.buildJobCollector() }, + "limitranges": func(b *Builder) *Collector { return b.buildLimitRangeCollector() }, "pods": func(b *Builder) *Collector { return b.buildPodCollector() }, "services": func(b *Builder) *Collector { return b.buildServiceCollector() }, // "configmaps": func(b *Builder) *Collector { return b.buildConfigMapCollector() }, @@ -143,13 +144,6 @@ var availableCollectors = map[string]func(f *Builder) *Collector{ // "statefulsets": func(b *Builder) *Collector { return b.buildStatefulSetCollector() }, } -// func (b *Builder) buildLimitRangeCollector() *Collector { -// store := metricsstore.NewMetricsStore(generateLimitRangeMetrics) -// reflectorPerNamespace(b.ctx, b.kubeClient, &v1.LimitRange{}, store, b.namespaces, createLimitRangeListWatch) -// -// return NewCollector(store) -// } - func (b *Builder) buildDaemonSetCollector() *Collector { filteredMetricFamilies := filterMetricFamilies(b.whiteBlackList, daemonSetMetricFamilies) composedMetricGenFuncs := composeMetricGenFuncs(filteredMetricFamilies) @@ -195,6 +189,21 @@ func (b *Builder) buildJobCollector() *Collector { return NewCollector(store) } +func (b *Builder) buildLimitRangeCollector() *Collector { + filteredMetricFamilies := filterMetricFamilies(b.whiteBlackList, limitRangeMetricFamilies) + composedMetricGenFuncs := composeMetricGenFuncs(filteredMetricFamilies) + + helpTexts := extractHelpText(filteredMetricFamilies) + + store := metricsstore.NewMetricsStore( + helpTexts, + composedMetricGenFuncs, + ) + reflectorPerNamespace(b.ctx, b.kubeClient, &v1.LimitRange{}, store, b.namespaces, createLimitRangeListWatch) + + return NewCollector(store) +} + func (b *Builder) buildServiceCollector() *Collector { filteredMetricFamilies := filterMetricFamilies(b.whiteBlackList, serviceMetricFamilies) composedMetricGenFuncs := composeMetricGenFuncs(filteredMetricFamilies) diff --git a/pkg/collectors/limitrange.go b/pkg/collectors/limitrange.go new file mode 100644 index 00000000..690bbc06 --- /dev/null +++ b/pkg/collectors/limitrange.go @@ -0,0 +1,129 @@ +/* +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 collectors + +import ( + "k8s.io/kube-state-metrics/pkg/metrics" + + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" + clientset "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/cache" +) + +var ( + descLimitRangeLabelsDefaultLabels = []string{"namespace", "limitrange"} + + limitRangeMetricFamilies = []metrics.FamilyGenerator{ + metrics.FamilyGenerator{ + Name: "kube_limitrange", + Help: "Information about limit range.", + GenerateFunc: wrapLimitRangeFunc(func(r *v1.LimitRange) metrics.Family { + f := metrics.Family{} + + rawLimitRanges := r.Spec.Limits + for _, rawLimitRange := range rawLimitRanges { + for resource, min := range rawLimitRange.Min { + f = append(f, &metrics.Metric{ + LabelValues: []string{string(resource), string(rawLimitRange.Type), "min"}, + Value: float64(min.MilliValue()) / 1000, + }) + } + + for resource, max := range rawLimitRange.Max { + f = append(f, &metrics.Metric{ + LabelValues: []string{string(resource), string(rawLimitRange.Type), "max"}, + Value: float64(max.MilliValue()) / 1000, + }) + } + + for resource, df := range rawLimitRange.Default { + f = append(f, &metrics.Metric{ + LabelValues: []string{string(resource), string(rawLimitRange.Type), "default"}, + Value: float64(df.MilliValue()) / 1000, + }) + } + + for resource, dfR := range rawLimitRange.DefaultRequest { + f = append(f, &metrics.Metric{ + LabelValues: []string{string(resource), string(rawLimitRange.Type), "defaultRequest"}, + Value: float64(dfR.MilliValue()) / 1000, + }) + } + + for resource, mLR := range rawLimitRange.MaxLimitRequestRatio { + f = append(f, &metrics.Metric{ + LabelValues: []string{string(resource), string(rawLimitRange.Type), "maxLimitRequestRatio"}, + Value: float64(mLR.MilliValue()) / 1000, + }) + } + } + + for _, m := range f { + m.Name = "kube_limitrange" + m.LabelKeys = []string{"resource", "type", "constraint"} + } + + return f + }), + }, + metrics.FamilyGenerator{ + Name: "kube_limitrange_created", + Help: "Unix creation timestamp", + GenerateFunc: wrapLimitRangeFunc(func(r *v1.LimitRange) metrics.Family { + f := metrics.Family{} + + if !r.CreationTimestamp.IsZero() { + f = append(f, &metrics.Metric{ + Name: "kube_limitrange_created", + Value: float64(r.CreationTimestamp.Unix()), + }) + } + + return f + }), + }, + } +) + +func wrapLimitRangeFunc(f func(*v1.LimitRange) metrics.Family) func(interface{}) metrics.Family { + return func(obj interface{}) metrics.Family { + limitRange := obj.(*v1.LimitRange) + + metricFamily := f(limitRange) + + for _, m := range metricFamily { + m.LabelKeys = append(descLimitRangeLabelsDefaultLabels, m.LabelKeys...) + m.LabelValues = append([]string{limitRange.Namespace, limitRange.Name}, m.LabelValues...) + } + + return metricFamily + } +} + +func createLimitRangeListWatch(kubeClient clientset.Interface, ns string) cache.ListWatch { + return cache.ListWatch{ + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { + return kubeClient.CoreV1().LimitRanges(ns).List(opts) + }, + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { + return kubeClient.CoreV1().LimitRanges(ns).Watch(opts) + }, + } +} diff --git a/pkg/collectors/limitrange_test.go b/pkg/collectors/limitrange_test.go new file mode 100644 index 00000000..251f7c6c --- /dev/null +++ b/pkg/collectors/limitrange_test.go @@ -0,0 +1,87 @@ +/* +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 collectors + +import ( + "testing" + "time" + + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestLimitRangeollector(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. + testMemory := "2.1G" + testMemoryQuantity := resource.MustParse(testMemory) + const metadata = ` + # HELP kube_limitrange_created Unix creation timestamp + # TYPE kube_limitrange_created gauge + # HELP kube_limitrange Information about limit range. + # TYPE kube_limitrange gauge + ` + cases := []generateMetricsTestCase{ + { + Obj: &v1.LimitRange{ + ObjectMeta: metav1.ObjectMeta{ + Name: "quotaTest", + CreationTimestamp: metav1.Time{Time: time.Unix(1500000000, 0)}, + Namespace: "testNS", + }, + Spec: v1.LimitRangeSpec{ + Limits: []v1.LimitRangeItem{ + { + Type: v1.LimitTypePod, + Max: map[v1.ResourceName]resource.Quantity{ + v1.ResourceMemory: testMemoryQuantity, + }, + Min: map[v1.ResourceName]resource.Quantity{ + v1.ResourceMemory: testMemoryQuantity, + }, + Default: map[v1.ResourceName]resource.Quantity{ + v1.ResourceMemory: testMemoryQuantity, + }, + DefaultRequest: map[v1.ResourceName]resource.Quantity{ + v1.ResourceMemory: testMemoryQuantity, + }, + MaxLimitRequestRatio: map[v1.ResourceName]resource.Quantity{ + v1.ResourceMemory: testMemoryQuantity, + }, + }, + }, + }, + }, + Want: ` + kube_limitrange_created{limitrange="quotaTest",namespace="testNS"} 1.5e+09 + kube_limitrange{constraint="default",limitrange="quotaTest",namespace="testNS",resource="memory",type="Pod"} 2.1e+09 + kube_limitrange{constraint="defaultRequest",limitrange="quotaTest",namespace="testNS",resource="memory",type="Pod"} 2.1e+09 + kube_limitrange{constraint="max",limitrange="quotaTest",namespace="testNS",resource="memory",type="Pod"} 2.1e+09 + kube_limitrange{constraint="maxLimitRequestRatio",limitrange="quotaTest",namespace="testNS",resource="memory",type="Pod"} 2.1e+09 + kube_limitrange{constraint="min",limitrange="quotaTest",namespace="testNS",resource="memory",type="Pod"} 2.1e+09 + + `, + }, + } + for i, c := range cases { + c.Func = composeMetricGenFuncs(limitRangeMetricFamilies) + if err := c.run(); err != nil { + t.Errorf("unexpected collecting result in %vth run:\n%s", i, err) + } + } +}