Skip to content

Commit 0cfbf0e

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 4bb1b38 commit 0cfbf0e

7 files changed

+616
-323
lines changed

pkg/customresourcestate/config.go

Lines changed: 95 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -21,29 +21,78 @@ import (
2121
"strings"
2222

2323
"github.com/gobuffalo/flect"
24-
2524
"k8s.io/klog/v2"
25+
26+
"k8s.io/kube-state-metrics/v2/pkg/customresource"
2627
)
2728

29+
// Metrics is the top level configuration object.
30+
type Metrics struct {
31+
Spec MetricsSpec `yaml:"spec" json:"spec"`
32+
}
33+
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"`
39+
}
40+
41+
// Resource configures a custom resource for metric generation.
42+
type Resource struct {
43+
// MetricNamePrefix defines a prefix for all metrics of the resource.
44+
// It defaults to the GroupVersionKind string, with invalid characters replaced by _.
45+
// If set to "_", no prefix will be added.
46+
// Example: If GroupVersionKind is "my-team.io/v1/MyResource", MetricNamePrefix will be "my_team_io_v1_MyResource".
47+
MetricNamePrefix string `yaml:"metricNamePrefix" json:"metricNamePrefix"`
48+
49+
// GroupVersionKind of the custom resource to be monitored.
50+
GroupVersionKind GroupVersionKind `yaml:"groupVersionKind" json:"groupVersionKind"`
51+
52+
// 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.
53+
Labels `yaml:",inline" json:",inline"`
54+
55+
// Metrics are the custom resource fields to be collected.
56+
Metrics []Generator `yaml:"metrics" json:"metrics"`
57+
// ErrorLogV defines the verbosity threshold for errors logged for this resource.
58+
ErrorLogV klog.Level `yaml:"errorLogV" json:"errorLogV"`
59+
60+
// ResourcePlural sets the plural name of the resource. Defaults to the plural version of the Kind according to flect.Pluralize.
61+
ResourcePlural string `yaml:"resourcePlural" json:"resourcePlural"`
62+
}
63+
64+
// GetMetricNamePrefix returns the prefix to use for metrics.
65+
func (r Resource) GetMetricNamePrefix() string {
66+
switch r.MetricNamePrefix {
67+
case "":
68+
return strings.NewReplacer(
69+
"/", "_",
70+
".", "_",
71+
"-", "_",
72+
).Replace(fmt.Sprintf("%s_%s_%s", r.GroupVersionKind.Group, r.GroupVersionKind.Version, r.GroupVersionKind.Kind))
73+
case "_":
74+
return ""
75+
default:
76+
return r.MetricNamePrefix
77+
}
78+
}
79+
80+
// GetResourceName returns the lowercase, plural form of the resource Kind. This is ResourcePlural if it is set.
81+
func (r Resource) GetResourceName() string {
82+
if r.ResourcePlural != "" {
83+
return r.ResourcePlural
84+
}
85+
// kubebuilder default:
86+
return strings.ToLower(flect.Pluralize(r.GroupVersionKind.Kind))
87+
}
88+
2889
// GroupVersionKind is the Kubernetes group, version, and kind of a resource.
2990
type GroupVersionKind struct {
3091
Group string `yaml:"group" json:"group"`
3192
Version string `yaml:"version" json:"version"`
3293
Kind string `yaml:"kind" json:"kind"`
3394
}
3495

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-
}
46-
4796
// Labels is common configuration of labels to add to metrics.
4897
type Labels struct {
4998
// CommonLabels are added to all metrics.
@@ -82,82 +131,49 @@ type Generator struct {
82131
// Help text for the metric.
83132
Help string `yaml:"help" json:"help"`
84133
// Each targets a value or values from the resource.
85-
Each MetricPer `yaml:"each" json:"each"`
134+
Each Metric `yaml:"each" json:"each"`
86135

87136
// 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
137+
Labels `yaml:",inline" json:",inline"` // json will inline because it is already tagged
89138
// ErrorLogV defines the verbosity threshold for errors logged for this metric. Must be non-zero to override the resource setting.
90139
ErrorLogV klog.Level `yaml:"errorLogV" json:"errorLogV"`
91140
}
92141

93-
// Resource configures a custom resource for metric generation.
94-
type Resource struct {
95-
// Namespace is an optional prefix for all metrics. Defaults to "kube" if not set. If set to "_", no namespace will be added.
96-
// The combination of Namespace and Subsystem will be prefixed to all metrics generated for this resource.
97-
// e.g., if Namespace is "kube" and Subsystem is "myteam_io_v1_MyResource", all metrics will be prefixed with "kube_myteam_io_v1_MyResource_".
98-
Namespace string `yaml:"namespace" json:"namespace"`
99-
// Subsystem defaults to the GroupVersionKind string, with invalid character replaced with _. If set to "_", no subsystem will be added.
100-
// e.g., if GroupVersionKind is "myteam.io/v1/MyResource", Subsystem will be "myteam_io_v1_MyResource".
101-
Subsystem string `yaml:"subsystem" json:"subsystem"`
102-
103-
// GroupVersionKind of the custom resource to be monitored.
104-
GroupVersionKind GroupVersionKind `yaml:"groupVersionKind" json:"groupVersionKind"`
105-
106-
// 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"`
108-
109-
// Metrics are the custom resource fields to be collected.
110-
Metrics []Generator `yaml:"metrics" json:"metrics"`
111-
// ErrorLogV defines the verbosity threshold for errors logged for this resource.
112-
ErrorLogV klog.Level `yaml:"errorLogV" json:"errorLogV"`
113-
114-
// ResourcePlural sets the plural name of the resource. Defaults to the plural version of the Kind according to flect.Pluralize.
115-
ResourcePlural string `yaml:"resourcePlural" json:"resourcePlural"`
142+
// Metric defines a metric to expose.
143+
// +union
144+
type Metric struct {
145+
// Type defines the type of the metric.
146+
// +unionDiscriminator
147+
Type MetricType `yaml:"type" json:"type"`
148+
149+
// Gauge defines a gauge metric.
150+
// +optional
151+
Gauge *MetricGauge `yaml:"gauge" json:"gauge"`
152+
// StateSet defines a state set metric.
153+
// +optional
154+
StateSet *MetricStateSet `yaml:"stateSet" json:"stateSet"`
155+
// Info defines a info metric.
156+
// +optional
157+
Info *MetricInfo `yaml:"info" json:"info"`
116158
}
117159

118-
// GetNamespace returns the namespace prefix to use for metrics.
119-
func (r Resource) GetNamespace() string {
120-
if r.Namespace == "" {
121-
return "kube"
122-
}
123-
if r.Namespace == "_" {
124-
return ""
125-
}
126-
return r.Namespace
160+
// ConfigDecoder is for use with FromConfig.
161+
type ConfigDecoder interface {
162+
Decode(v interface{}) (err error)
127163
}
128164

129-
// GetSubsystem returns the subsystem prefix to use for metrics (will be joined between namespace and the metric name).
130-
func (r Resource) GetSubsystem() string {
131-
if r.Subsystem == "" {
132-
return strings.NewReplacer(
133-
"/", "_",
134-
".", "_",
135-
"-", "_",
136-
).Replace(fmt.Sprintf("%s_%s_%s", r.GroupVersionKind.Group, r.GroupVersionKind.Version, r.GroupVersionKind.Kind))
137-
}
138-
if r.Subsystem == "_" {
139-
return ""
165+
// FromConfig decodes a configuration source into a slice of customresource.RegistryFactory that are ready to use.
166+
func FromConfig(decoder ConfigDecoder) (factories []customresource.RegistryFactory, err error) {
167+
var crconfig Metrics
168+
if err := decoder.Decode(&crconfig); err != nil {
169+
return nil, fmt.Errorf("failed to parse Custom Resource State metrics: %w", err)
140170
}
141-
return r.Subsystem
142-
}
143-
144-
// GetResourceName returns the lowercase, plural form of the resource Kind. This is ResourcePlural if it is set.
145-
func (r Resource) GetResourceName() string {
146-
if r.ResourcePlural != "" {
147-
return r.ResourcePlural
171+
for _, resource := range crconfig.Spec.Resources {
172+
factory, err := NewCustomResourceMetrics(resource)
173+
if err != nil {
174+
return nil, fmt.Errorf("failed to create metrics factory for %s: %w", resource.GroupVersionKind, err)
175+
}
176+
factories = append(factories, factory)
148177
}
149-
// kubebuilder default:
150-
return strings.ToLower(flect.Pluralize(r.GroupVersionKind.Kind))
151-
}
152-
153-
// Metrics is the top level configuration object.
154-
type Metrics struct {
155-
Spec MetricsSpec `yaml:"spec" json:"spec"`
156-
}
157-
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"`
178+
return factories, nil
163179
}
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)