Skip to content

Commit 6c07045

Browse files
authored
Merge pull request #488 from brancz/limit
Allow white- and blacklisting metrics to be exposed
2 parents cb6d547 + c522d44 commit 6c07045

File tree

6 files changed

+279
-3
lines changed

6 files changed

+279
-3
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
* [CHANGE] `kube_job_status_start_time` and `kube_job_status_completion_time` metric types changed from counter to gauge.
44
* [CHANGE] `job` label to `job_name` as this collides with the Prometheus `job` label.
5+
* [FEATURE] Allow white- and black-listing metrics to be exposed.
56

67
## v1.3.1 / 2018-04-12
78

main.go

+16-2
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,11 @@ import (
3131
"github.com/prometheus/client_golang/prometheus"
3232
"github.com/prometheus/client_golang/prometheus/promhttp"
3333
clientset "k8s.io/client-go/kubernetes"
34+
_ "k8s.io/client-go/plugin/pkg/client/auth"
3435
"k8s.io/client-go/tools/clientcmd"
3536

36-
_ "k8s.io/client-go/plugin/pkg/client/auth"
3737
kcollectors "k8s.io/kube-state-metrics/pkg/collectors"
38+
"k8s.io/kube-state-metrics/pkg/metrics"
3839
"k8s.io/kube-state-metrics/pkg/options"
3940
"k8s.io/kube-state-metrics/pkg/version"
4041
)
@@ -91,6 +92,19 @@ func main() {
9192
glog.Infof("Using %s namespaces", namespaces)
9293
}
9394

95+
if opts.MetricWhitelist.IsEmpty() && opts.MetricBlacklist.IsEmpty() {
96+
glog.Info("No metric whitelist or blacklist set. No filtering of metrics will be done.")
97+
}
98+
if !opts.MetricWhitelist.IsEmpty() && !opts.MetricBlacklist.IsEmpty() {
99+
glog.Fatal("Whitelist and blacklist are both set. They are mutually exclusive, only one of them can be set.")
100+
}
101+
if !opts.MetricWhitelist.IsEmpty() {
102+
glog.Infof("A metric whitelist has been configured. Only the following metrics will be exposed: %s.", opts.MetricWhitelist.String())
103+
}
104+
if !opts.MetricBlacklist.IsEmpty() {
105+
glog.Infof("A metric blacklist has been configured. The following metrics will not be exposed: %s.", opts.MetricBlacklist.String())
106+
}
107+
94108
proc.StartReaper()
95109

96110
kubeClient, err := createKubeClient(opts.Apiserver, opts.Kubeconfig)
@@ -107,7 +121,7 @@ func main() {
107121

108122
registry := prometheus.NewRegistry()
109123
registerCollectors(registry, kubeClient, collectors, namespaces, opts)
110-
metricsServer(registry, opts.Host, opts.Port)
124+
metricsServer(metrics.FilteredGatherer(registry, opts.MetricWhitelist, opts.MetricBlacklist), opts.Host, opts.Port)
111125
}
112126

113127
func createKubeClient(apiserver string, kubeconfig string) (clientset.Interface, error) {

pkg/metrics/metrics.go

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
Copyright 2018 The Kubernetes Authors All rights reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package metrics
18+
19+
import (
20+
"github.com/prometheus/client_golang/prometheus"
21+
dto "github.com/prometheus/client_model/go"
22+
23+
"k8s.io/kube-state-metrics/pkg/options"
24+
)
25+
26+
type gathererFunc func() ([]*dto.MetricFamily, error)
27+
28+
func (f gathererFunc) Gather() ([]*dto.MetricFamily, error) {
29+
return f()
30+
}
31+
32+
// FilteredGatherer wraps a prometheus.Gatherer to filter metrics based on a
33+
// white or blacklist. Whitelist and blacklist are mutually exclusive.
34+
func FilteredGatherer(r prometheus.Gatherer, whitelist options.MetricSet, blacklist options.MetricSet) prometheus.Gatherer {
35+
whitelistEnabled := !whitelist.IsEmpty()
36+
blacklistEnabled := !blacklist.IsEmpty()
37+
38+
if whitelistEnabled {
39+
return gathererFunc(func() ([]*dto.MetricFamily, error) {
40+
metricFamilies, err := r.Gather()
41+
if err != nil {
42+
return nil, err
43+
}
44+
45+
newMetricFamilies := []*dto.MetricFamily{}
46+
for _, metricFamily := range metricFamilies {
47+
// deferencing this string may be a performance bottleneck
48+
name := *metricFamily.Name
49+
_, onWhitelist := whitelist[name]
50+
if onWhitelist {
51+
newMetricFamilies = append(newMetricFamilies, metricFamily)
52+
}
53+
}
54+
55+
return newMetricFamilies, nil
56+
})
57+
}
58+
59+
if blacklistEnabled {
60+
return gathererFunc(func() ([]*dto.MetricFamily, error) {
61+
metricFamilies, err := r.Gather()
62+
if err != nil {
63+
return nil, err
64+
}
65+
66+
newMetricFamilies := []*dto.MetricFamily{}
67+
for _, metricFamily := range metricFamilies {
68+
name := *metricFamily.Name
69+
_, onBlacklist := blacklist[name]
70+
if onBlacklist {
71+
continue
72+
}
73+
newMetricFamilies = append(newMetricFamilies, metricFamily)
74+
}
75+
76+
return newMetricFamilies, nil
77+
})
78+
}
79+
80+
return r
81+
}

pkg/metrics/metrics_test.go

+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package metrics
2+
3+
import (
4+
"testing"
5+
6+
"github.com/prometheus/client_golang/prometheus"
7+
"k8s.io/kube-state-metrics/pkg/options"
8+
)
9+
10+
func TestFiltererdGatherer(t *testing.T) {
11+
r := prometheus.NewRegistry()
12+
c1 := prometheus.NewCounter(
13+
prometheus.CounterOpts{
14+
Name: "test1",
15+
Help: "test1 help",
16+
},
17+
)
18+
c2 := prometheus.NewCounter(
19+
prometheus.CounterOpts{
20+
Name: "test2",
21+
Help: "test2 help",
22+
},
23+
)
24+
c1.Inc()
25+
c1.Inc()
26+
c2.Inc()
27+
r.MustRegister(c1)
28+
r.MustRegister(c2)
29+
30+
res, err := FilteredGatherer(r, nil, nil).Gather()
31+
if err != nil {
32+
t.Fatal(err)
33+
}
34+
35+
found1 := false
36+
found2 := false
37+
for _, mf := range res {
38+
if *mf.Name == "test1" {
39+
found1 = true
40+
}
41+
if *mf.Name == "test2" {
42+
found2 = true
43+
}
44+
}
45+
46+
if !found1 || !found2 {
47+
t.Fatal("No results expected to be filtered, but results were filtered.")
48+
}
49+
}
50+
51+
func TestFiltererdGathererWhitelist(t *testing.T) {
52+
r := prometheus.NewRegistry()
53+
c1 := prometheus.NewCounter(
54+
prometheus.CounterOpts{
55+
Name: "test1",
56+
Help: "test1 help",
57+
},
58+
)
59+
c2 := prometheus.NewCounter(
60+
prometheus.CounterOpts{
61+
Name: "test2",
62+
Help: "test2 help",
63+
},
64+
)
65+
c1.Inc()
66+
c1.Inc()
67+
c2.Inc()
68+
r.MustRegister(c1)
69+
r.MustRegister(c2)
70+
71+
whitelist := options.MetricSet{}
72+
whitelist.Set("test1")
73+
74+
res, err := FilteredGatherer(r, whitelist, nil).Gather()
75+
if err != nil {
76+
t.Fatal(err)
77+
}
78+
79+
found1 := false
80+
found2 := false
81+
for _, mf := range res {
82+
if *mf.Name == "test1" {
83+
found1 = true
84+
}
85+
if *mf.Name == "test2" {
86+
found2 = true
87+
}
88+
}
89+
90+
if !found1 || found2 {
91+
t.Fatalf("Expected `test2` to be filtered and `test1` not. `test1`: %t ; `test2`: %t.", found1, found2)
92+
}
93+
}
94+
95+
func TestFiltererdGathererBlacklist(t *testing.T) {
96+
r := prometheus.NewRegistry()
97+
c1 := prometheus.NewCounter(
98+
prometheus.CounterOpts{
99+
Name: "test1",
100+
Help: "test1 help",
101+
},
102+
)
103+
c2 := prometheus.NewCounter(
104+
prometheus.CounterOpts{
105+
Name: "test2",
106+
Help: "test2 help",
107+
},
108+
)
109+
c1.Inc()
110+
c1.Inc()
111+
c2.Inc()
112+
r.MustRegister(c1)
113+
r.MustRegister(c2)
114+
115+
blacklist := options.MetricSet{}
116+
blacklist.Set("test1")
117+
118+
res, err := FilteredGatherer(r, nil, blacklist).Gather()
119+
if err != nil {
120+
t.Fatal(err)
121+
}
122+
123+
found1 := false
124+
found2 := false
125+
for _, mf := range res {
126+
if *mf.Name == "test1" {
127+
found1 = true
128+
}
129+
if *mf.Name == "test2" {
130+
found2 = true
131+
}
132+
}
133+
134+
if found1 || !found2 {
135+
t.Fatalf("Expected `test1` to be filtered and `test2` not. `test1`: %t ; `test2`: %t.", found1, found2)
136+
}
137+
}

pkg/options/options.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ type Options struct {
3434
TelemetryHost string
3535
Collectors CollectorSet
3636
Namespaces NamespaceList
37+
MetricBlacklist MetricSet
38+
MetricWhitelist MetricSet
3739
Version bool
3840
DisablePodNonGenericResourceMetrics bool
3941
DisableNodeNonGenericResourceMetrics bool
@@ -43,7 +45,9 @@ type Options struct {
4345

4446
func NewOptions() *Options {
4547
return &Options{
46-
Collectors: CollectorSet{},
48+
Collectors: CollectorSet{},
49+
MetricWhitelist: MetricSet{},
50+
MetricBlacklist: MetricSet{},
4751
}
4852
}
4953

@@ -69,6 +73,8 @@ func (o *Options) AddFlags() {
6973
o.flags.StringVar(&o.TelemetryHost, "telemetry-host", "0.0.0.0", `Host to expose kube-state-metrics self metrics on.`)
7074
o.flags.Var(&o.Collectors, "collectors", fmt.Sprintf("Comma-separated list of collectors to be enabled. Defaults to %q", &DefaultCollectors))
7175
o.flags.Var(&o.Namespaces, "namespace", fmt.Sprintf("Comma-separated list of namespaces to be enabled. Defaults to %q", &DefaultNamespaces))
76+
o.flags.Var(&o.MetricWhitelist, "metric-whitelist", "Comma-separated list of metrics to be exposed. The whitelist and blacklist are mutually exclusive.")
77+
o.flags.Var(&o.MetricBlacklist, "metric-blacklist", "Comma-separated list of metrics not to be enabled. The whitelist and blacklist are mutually exclusive.")
7278
o.flags.BoolVarP(&o.Version, "version", "", false, "kube-state-metrics build version information")
7379
o.flags.BoolVarP(&o.DisablePodNonGenericResourceMetrics, "disable-pod-non-generic-resource-metrics", "", false, "Disable pod non generic resource request and limit metrics")
7480
o.flags.BoolVarP(&o.DisableNodeNonGenericResourceMetrics, "disable-node-non-generic-resource-metrics", "", false, "Disable node non generic resource request and limit metrics")

pkg/options/types.go

+37
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,43 @@ import (
2525
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2626
)
2727

28+
type MetricSet map[string]struct{}
29+
30+
func (ms *MetricSet) String() string {
31+
s := *ms
32+
ss := s.asSlice()
33+
sort.Strings(ss)
34+
return strings.Join(ss, ",")
35+
}
36+
37+
func (ms *MetricSet) Set(value string) error {
38+
s := *ms
39+
metrics := strings.Split(value, ",")
40+
for _, metric := range metrics {
41+
metric = strings.TrimSpace(metric)
42+
if len(metric) != 0 {
43+
s[metric] = struct{}{}
44+
}
45+
}
46+
return nil
47+
}
48+
49+
func (ms MetricSet) asSlice() []string {
50+
metrics := []string{}
51+
for metric := range ms {
52+
metrics = append(metrics, metric)
53+
}
54+
return metrics
55+
}
56+
57+
func (ms MetricSet) IsEmpty() bool {
58+
return len(ms.asSlice()) == 0
59+
}
60+
61+
func (ms *MetricSet) Type() string {
62+
return "string"
63+
}
64+
2865
type CollectorSet map[string]struct{}
2966

3067
func (c *CollectorSet) String() string {

0 commit comments

Comments
 (0)