Skip to content

Commit f2659da

Browse files
committed
pkg/customresourcestate implement info and stateSet metric type and refactor configuration file
* Adds detection of booleans in string format to getNum. * Refactors configuration file to allow definition of different metric types having different configuration variables. * Refactor order of types and funcs in pkg/customersourcestate. * Allows info and stateSet metrics to iterate over arrays. * Adds `nilIsZero` config variable to gauge to indicate non-existing values to tread as 0 value instead of returning an error. * Skip adding a label instead of setting value to `<nil>`. crd allow info and stateSet metrics to expose values from arrays
1 parent e4011f8 commit f2659da

7 files changed

+610
-300
lines changed

pkg/customresourcestate/config.go

Lines changed: 99 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -21,73 +21,21 @@ import (
2121
"strings"
2222

2323
"github.com/gobuffalo/flect"
24-
2524
"k8s.io/klog/v2"
26-
)
27-
28-
// GroupVersionKind is the Kubernetes group, version, and kind of a resource.
29-
type GroupVersionKind struct {
30-
Group string `yaml:"group" json:"group"`
31-
Version string `yaml:"version" json:"version"`
32-
Kind string `yaml:"kind" json:"kind"`
33-
}
34-
35-
// MetricPer targets a Path that may be a single value, array, or object. Arrays and objects will generate a metric per element.
36-
type MetricPer struct {
37-
// Path is the path to the value to generate metric(s) for.
38-
Path []string `yaml:"path" json:"path"`
39-
// ValueFrom is the path to a numeric field under Path that will be the metric value.
40-
ValueFrom []string `yaml:"valueFrom" json:"valueFrom"`
41-
// LabelFromKey adds a label with the given name if Path is an object. The label value will be the object key.
42-
LabelFromKey string `yaml:"labelFromKey" json:"labelFromKey"`
43-
// LabelsFromPath adds additional labels where the value of the label is taken from a field under Path.
44-
LabelsFromPath map[string][]string `yaml:"labelsFromPath" json:"labelsFromPath"`
45-
}
4625

47-
// Labels is common configuration of labels to add to metrics.
48-
type Labels struct {
49-
// CommonLabels are added to all metrics.
50-
CommonLabels map[string]string `yaml:"commonLabels" json:"commonLabels"`
51-
// LabelsFromPath adds additional labels where the value is taken from a field in the resource.
52-
LabelsFromPath map[string][]string `yaml:"labelsFromPath" json:"labelsFromPath"`
53-
}
54-
55-
// Merge combines the labels from two configs, returning a new config. The other Labels will overwrite keys in this Labels.
56-
func (l Labels) Merge(other Labels) Labels {
57-
common := make(map[string]string)
58-
paths := make(map[string][]string)
26+
"k8s.io/kube-state-metrics/v2/pkg/customresource"
27+
)
5928

60-
for k, v := range l.CommonLabels {
61-
common[k] = v
62-
}
63-
for k, v := range l.LabelsFromPath {
64-
paths[k] = v
65-
}
66-
for k, v := range other.CommonLabels {
67-
common[k] = v
68-
}
69-
for k, v := range other.LabelsFromPath {
70-
paths[k] = v
71-
}
72-
return Labels{
73-
CommonLabels: common,
74-
LabelsFromPath: paths,
75-
}
29+
// Metrics is the top level configuration object.
30+
type Metrics struct {
31+
Spec MetricsSpec `yaml:"spec" json:"spec"`
7632
}
7733

78-
// Generator describes a unique metric name.
79-
type Generator struct {
80-
// Name of the metric. Subject to prefixing based on the configuration of the Resource.
81-
Name string `yaml:"name" json:"name"`
82-
// Help text for the metric.
83-
Help string `yaml:"help" json:"help"`
84-
// Each targets a value or values from the resource.
85-
Each MetricPer `yaml:"each" json:"each"`
86-
87-
// Labels are added to all metrics. Labels from Each will overwrite these if using the same key.
88-
Labels `yaml:",inline"` // json will inline because it is already tagged
89-
// ErrorLogV defines the verbosity threshold for errors logged for this metric. Must be non-zero to override the resource setting.
90-
ErrorLogV klog.Level `yaml:"errorLogV" json:"errorLogV"`
34+
// MetricsSpec is the configuration describing the custom resource state metrics to generate.
35+
type MetricsSpec struct {
36+
// Resources is the list of custom resources to be monitored. A resource with the same GroupVersionKind may appear
37+
// multiple times (e.g., to customize the namespace or subsystem,) but will incur additional overhead.
38+
Resources []Resource `yaml:"resources" json:"resources"`
9139
}
9240

9341
// Resource configures a custom resource for metric generation.
@@ -104,7 +52,7 @@ type Resource struct {
10452
GroupVersionKind GroupVersionKind `yaml:"groupVersionKind" json:"groupVersionKind"`
10553

10654
// Labels are added to all metrics. If the same key is used in a metric, the value from the metric will overwrite the value here.
107-
Labels `yaml:",inline"`
55+
Labels `yaml:",inline" json:",inline"`
10856

10957
// Metrics are the custom resource fields to be collected.
11058
Metrics []Generator `yaml:"metrics" json:"metrics"`
@@ -150,14 +98,94 @@ func (r Resource) GetResourceName() string {
15098
return strings.ToLower(flect.Pluralize(r.GroupVersionKind.Kind))
15199
}
152100

153-
// Metrics is the top level configuration object.
154-
type Metrics struct {
155-
Spec MetricsSpec `yaml:"spec" json:"spec"`
101+
// GroupVersionKind is the Kubernetes group, version, and kind of a resource.
102+
type GroupVersionKind struct {
103+
Group string `yaml:"group" json:"group"`
104+
Version string `yaml:"version" json:"version"`
105+
Kind string `yaml:"kind" json:"kind"`
156106
}
157107

158-
// MetricsSpec is the configuration describing the custom resource state metrics to generate.
159-
type MetricsSpec struct {
160-
// Resources is the list of custom resources to be monitored. A resource with the same GroupVersionKind may appear
161-
// multiple times (e.g., to customize the namespace or subsystem,) but will incur additional overhead.
162-
Resources []Resource `yaml:"resources" json:"resources"`
108+
// Labels is common configuration of labels to add to metrics.
109+
type Labels struct {
110+
// CommonLabels are added to all metrics.
111+
CommonLabels map[string]string `yaml:"commonLabels" json:"commonLabels"`
112+
// LabelsFromPath adds additional labels where the value is taken from a field in the resource.
113+
LabelsFromPath map[string][]string `yaml:"labelsFromPath" json:"labelsFromPath"`
114+
}
115+
116+
// Merge combines the labels from two configs, returning a new config. The other Labels will overwrite keys in this Labels.
117+
func (l Labels) Merge(other Labels) Labels {
118+
common := make(map[string]string)
119+
paths := make(map[string][]string)
120+
121+
for k, v := range l.CommonLabels {
122+
common[k] = v
123+
}
124+
for k, v := range l.LabelsFromPath {
125+
paths[k] = v
126+
}
127+
for k, v := range other.CommonLabels {
128+
common[k] = v
129+
}
130+
for k, v := range other.LabelsFromPath {
131+
paths[k] = v
132+
}
133+
return Labels{
134+
CommonLabels: common,
135+
LabelsFromPath: paths,
136+
}
137+
}
138+
139+
// Generator describes a unique metric name.
140+
type Generator struct {
141+
// Name of the metric. Subject to prefixing based on the configuration of the Resource.
142+
Name string `yaml:"name" json:"name"`
143+
// Help text for the metric.
144+
Help string `yaml:"help" json:"help"`
145+
// Each targets a value or values from the resource.
146+
Each Metric `yaml:"each" json:"each"`
147+
148+
// Labels are added to all metrics. Labels from Each will overwrite these if using the same key.
149+
Labels `yaml:",inline" json:",inline"` // json will inline because it is already tagged
150+
// ErrorLogV defines the verbosity threshold for errors logged for this metric. Must be non-zero to override the resource setting.
151+
ErrorLogV klog.Level `yaml:"errorLogV" json:"errorLogV"`
152+
}
153+
154+
// Metric defines a metric to expose.
155+
// +union
156+
type Metric struct {
157+
// Type defines the type of the metric.
158+
// +unionDiscriminator
159+
Type MetricType `yaml:"type" json:"type"`
160+
161+
// Gauge defines a gauge metric.
162+
// +optional
163+
Gauge *MetricGauge `yaml:"gauge" json:"gauge"`
164+
// StateSet defines a state set metric.
165+
// +optional
166+
StateSet *MetricStateSet `yaml:"stateSet" json:"stateSet"`
167+
// Info defines a info metric.
168+
// +optional
169+
Info *MetricInfo `yaml:"info" json:"info"`
170+
}
171+
172+
// ConfigDecoder is for use with FromConfig.
173+
type ConfigDecoder interface {
174+
Decode(v interface{}) (err error)
175+
}
176+
177+
// FromConfig decodes a configuration source into a slice of customresource.RegistryFactory that are ready to use.
178+
func FromConfig(decoder ConfigDecoder) (factories []customresource.RegistryFactory, err error) {
179+
var crconfig Metrics
180+
if err := decoder.Decode(&crconfig); err != nil {
181+
return nil, fmt.Errorf("failed to parse Custom Resource State metrics: %w", err)
182+
}
183+
for _, resource := range crconfig.Spec.Resources {
184+
factory, err := NewCustomResourceMetrics(resource)
185+
if err != nil {
186+
return nil, fmt.Errorf("failed to create metrics factory for %s: %w", resource.GroupVersionKind, err)
187+
}
188+
factories = append(factories, factory)
189+
}
190+
return factories, nil
163191
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
Copyright 2021 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 customresourcestate
18+
19+
// MetricType is the type of a metric.
20+
type MetricType string
21+
22+
// Supported metric types.
23+
const (
24+
MetricTypeGauge MetricType = "Gauge"
25+
MetricTypeStateSet MetricType = "StateSet"
26+
MetricTypeInfo MetricType = "Info"
27+
)
28+
29+
// MetricMeta are variables which may used for any metric type.
30+
type MetricMeta struct {
31+
// LabelsFromPath adds additional labels where the value of the label is taken from a field under Path.
32+
LabelsFromPath map[string][]string `yaml:"labelsFromPath" json:"labelsFromPath"`
33+
// Path is the path to to generate metric(s) for.
34+
Path []string `yaml:"path" json:"path"`
35+
}
36+
37+
// MetricGauge targets a Path that may be a single value, array, or object. Arrays and objects will generate a metric per element.
38+
// Ref: https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#gauge
39+
type MetricGauge struct {
40+
MetricMeta `yaml:",inline" json:",inline"`
41+
42+
// ValueFrom is the path to a numeric field under Path that will be the metric value.
43+
ValueFrom []string `yaml:"valueFrom" json:"valueFrom"`
44+
// LabelFromKey adds a label with the given name if Path is an object. The label value will be the object key.
45+
LabelFromKey string `yaml:"labelFromKey" json:"labelFromKey"`
46+
// NilIsZero indicates that if a value is nil it will be treated as zero value.
47+
NilIsZero bool `yaml:"nilIsZero" json:"nilIsZero"`
48+
}
49+
50+
// MetricInfo is a metric which is used to expose textual information.
51+
// Ref: https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#info
52+
type MetricInfo struct {
53+
MetricMeta `yaml:",inline" json:",inline"`
54+
}
55+
56+
// MetricStateSet is a metric which represent a series of related boolean values, also called a bitset.
57+
// Ref: https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#stateset
58+
type MetricStateSet struct {
59+
MetricMeta `yaml:",inline" json:",inline"`
60+
61+
// List is the list of values to expose a value for.
62+
List []string `yaml:"list" json:"list"`
63+
// LabelName is the key of the label which is used for each entry in List to expose the value.
64+
LabelName string `yaml:"labelName" json:"labelName"`
65+
// ValueFrom is the subpath to compare the list to.
66+
ValueFrom []string `yaml:"valueFrom" json:"valueFrom"`
67+
}

pkg/customresourcestate/config_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,21 @@ func Test_Metrics_deserialization(t *testing.T) {
3535
assert.Equal(t, "active_count", m.Spec.Resources[0].Metrics[0].Name)
3636

3737
t.Run("can create resource factory", func(t *testing.T) {
38-
rf, err := NewFieldMetrics(m.Spec.Resources[0])
38+
rf, err := NewCustomResourceMetrics(m.Spec.Resources[0])
3939
assert.NoError(t, err)
4040

4141
t.Run("labels are merged", func(t *testing.T) {
4242
assert.Equal(t, map[string]string{
4343
"name": mustCompilePath(t, "metadata", "name").String(),
44-
}, toPaths(rf.(*fieldMetrics).Families[1].LabelFromPath))
44+
}, toPaths(rf.(*customResourceMetrics).Families[1].LabelFromPath))
4545
})
4646

4747
t.Run("errorLogV", func(t *testing.T) {
48-
assert.Equal(t, klog.Level(5), rf.(*fieldMetrics).Families[1].ErrorLogV)
48+
assert.Equal(t, klog.Level(5), rf.(*customResourceMetrics).Families[1].ErrorLogV)
4949
})
5050

5151
t.Run("resource name", func(t *testing.T) {
52-
assert.Equal(t, rf.(*fieldMetrics).ResourceName, "foos")
52+
assert.Equal(t, rf.(*customResourceMetrics).ResourceName, "foos")
5353
})
5454
})
5555
}

0 commit comments

Comments
 (0)