Skip to content

Commit fa5eb67

Browse files
authored
Merge pull request #2270 from rexagod/2248
fix: fallback to `gauge` for `protofmt`-based negotiations
2 parents 0a8b3b4 + 2a06c45 commit fa5eb67

File tree

9 files changed

+246
-48
lines changed

9 files changed

+246
-48
lines changed

pkg/customresourcestate/config.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import (
2020
"fmt"
2121
"strings"
2222

23+
"k8s.io/kube-state-metrics/v2/pkg/metric"
24+
2325
"github.com/gobuffalo/flect"
2426
"k8s.io/apimachinery/pkg/runtime/schema"
2527
"k8s.io/klog/v2"
@@ -148,7 +150,7 @@ type Generator struct {
148150
type Metric struct {
149151
// Type defines the type of the metric.
150152
// +unionDiscriminator
151-
Type MetricType `yaml:"type" json:"type"`
153+
Type metric.Type `yaml:"type" json:"type"`
152154

153155
// Gauge defines a gauge metric.
154156
// +optional
@@ -170,9 +172,16 @@ type ConfigDecoder interface {
170172
func FromConfig(decoder ConfigDecoder, discovererInstance *discovery.CRDiscoverer) (func() ([]customresource.RegistryFactory, error), error) {
171173
var customResourceConfig Metrics
172174
factoriesIndex := map[string]bool{}
175+
176+
// Decode the configuration.
173177
if err := decoder.Decode(&customResourceConfig); err != nil {
174178
return nil, fmt.Errorf("failed to parse Custom Resource State metrics: %w", err)
175179
}
180+
181+
// Override the configuration with any custom overrides.
182+
configOverrides(&customResourceConfig)
183+
184+
// Create a factory for each resource.
176185
fn := func() (factories []customresource.RegistryFactory, err error) {
177186
resources := customResourceConfig.Spec.Resources
178187
// resolvedGVKPs will have the final list of GVKs, in addition to the resolved G** resources.
@@ -206,3 +215,15 @@ func FromConfig(decoder ConfigDecoder, discovererInstance *discovery.CRDiscovere
206215
}
207216
return fn, nil
208217
}
218+
219+
// configOverrides applies overrides to the configuration.
220+
func configOverrides(config *Metrics) {
221+
for i := range config.Spec.Resources {
222+
for j := range config.Spec.Resources[i].Metrics {
223+
224+
// Override the metric type to lowercase, so the internals have a single source of truth for metric type definitions.
225+
// This is done as a convenience measure for users, so they don't have to remember the exact casing.
226+
config.Spec.Resources[i].Metrics[j].Each.Type = metric.Type(strings.ToLower(string(config.Spec.Resources[i].Metrics[j].Each.Type)))
227+
}
228+
}
229+
}

pkg/customresourcestate/config_metrics_types.go

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,6 @@ limitations under the License.
1616

1717
package customresourcestate
1818

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-
2919
// MetricMeta are variables which may used for any metric type.
3020
type MetricMeta struct {
3121
// LabelsFromPath adds additional labels where the value of the label is taken from a field under Path.

pkg/customresourcestate/config_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ var testData string
3232
func Test_Metrics_deserialization(t *testing.T) {
3333
var m Metrics
3434
assert.NoError(t, yaml.NewDecoder(strings.NewReader(testData)).Decode(&m))
35+
configOverrides(&m)
3536
assert.Equal(t, "active_count", m.Spec.Resources[0].Metrics[0].Name)
3637

3738
t.Run("can create resource factory", func(t *testing.T) {

pkg/customresourcestate/custom_resource_metrics_test.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import (
2121
"reflect"
2222
"testing"
2323

24+
"k8s.io/kube-state-metrics/v2/pkg/metric"
25+
2426
"k8s.io/apimachinery/pkg/runtime/schema"
2527
"k8s.io/utils/ptr"
2628
)
@@ -55,7 +57,7 @@ func TestNewCustomResourceMetrics(t *testing.T) {
5557
Name: "test_metrics",
5658
Help: "metrics for testing",
5759
Each: Metric{
58-
Type: MetricTypeInfo,
60+
Type: metric.Info,
5961
Info: &MetricInfo{
6062
MetricMeta: MetricMeta{
6163
Path: []string{
@@ -117,7 +119,7 @@ func TestNewCustomResourceMetrics(t *testing.T) {
117119
Name: "test_metrics",
118120
Help: "metrics for testing",
119121
Each: Metric{
120-
Type: MetricTypeInfo,
122+
Type: metric.Info,
121123
Info: &MetricInfo{
122124
MetricMeta: MetricMeta{
123125
Path: []string{
@@ -180,7 +182,7 @@ func TestNewCustomResourceMetrics(t *testing.T) {
180182
Name: "test_metrics",
181183
Help: "metrics for testing",
182184
Each: Metric{
183-
Type: MetricTypeInfo,
185+
Type: metric.Info,
184186
Info: &MetricInfo{
185187
MetricMeta: MetricMeta{
186188
Path: []string{

pkg/customresourcestate/registry_factory.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ func compileCommon(c MetricMeta) (*compiledCommon, error) {
7272
func compileFamily(f Generator, resource Resource) (*compiledFamily, error) {
7373
labels := resource.Labels.Merge(f.Labels)
7474

75-
if f.Each.Type == MetricTypeInfo && !strings.HasSuffix(f.Name, "_info") {
75+
if f.Each.Type == metric.Info && !strings.HasSuffix(f.Name, "_info") {
7676
klog.InfoS("Info metric does not have _info suffix", "gvk", resource.GroupVersionKind.String(), "name", f.Name)
7777
}
7878

@@ -153,7 +153,7 @@ type compiledMetric interface {
153153
// newCompiledMetric returns a compiledMetric depending on the given metric type.
154154
func newCompiledMetric(m Metric) (compiledMetric, error) {
155155
switch m.Type {
156-
case MetricTypeGauge:
156+
case metric.Gauge:
157157
if m.Gauge == nil {
158158
return nil, errors.New("expected each.gauge to not be nil")
159159
}
@@ -172,7 +172,7 @@ func newCompiledMetric(m Metric) (compiledMetric, error) {
172172
NilIsZero: m.Gauge.NilIsZero,
173173
labelFromKey: m.Gauge.LabelFromKey,
174174
}, nil
175-
case MetricTypeInfo:
175+
case metric.Info:
176176
if m.Info == nil {
177177
return nil, errors.New("expected each.info to not be nil")
178178
}
@@ -185,7 +185,7 @@ func newCompiledMetric(m Metric) (compiledMetric, error) {
185185
compiledCommon: *cc,
186186
labelFromKey: m.Info.LabelFromKey,
187187
}, nil
188-
case MetricTypeStateSet:
188+
case metric.StateSet:
189189
if m.StateSet == nil {
190190
return nil, errors.New("expected each.stateSet to not be nil")
191191
}

pkg/metric/metric.go

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,21 +37,24 @@ var (
3737
}
3838
)
3939

40-
// Type represents the type of a metric e.g. a counter. See
41-
// https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#metric-types.
40+
// Type represents the type of the metric. See https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#metric-types.
4241
type Type string
4342

44-
// Gauge defines a OpenMetrics gauge.
45-
var Gauge Type = "gauge"
43+
// Supported metric types.
44+
var (
45+
46+
// Gauge defines an OpenMetrics gauge.
47+
Gauge Type = "gauge"
4648

47-
// Info defines an OpenMetrics info.
48-
var Info Type = "info"
49+
// Info defines an OpenMetrics info.
50+
Info Type = "info"
4951

50-
// StateSet defines an OpenMetrics stateset.
51-
var StateSet Type = "stateset"
52+
// StateSet defines an OpenMetrics stateset.
53+
StateSet Type = "stateset"
5254

53-
// Counter defines a OpenMetrics counter.
54-
var Counter Type = "counter"
55+
// Counter defines an OpenMetrics counter.
56+
Counter Type = "counter"
57+
)
5558

5659
// Metric represents a single time series.
5760
type Metric struct {

pkg/metrics_store/metrics_writer.go

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ package metricsstore
1919
import (
2020
"fmt"
2121
"io"
22+
"strings"
23+
24+
"github.com/prometheus/common/expfmt"
25+
26+
"k8s.io/kube-state-metrics/v2/pkg/metric"
2227
)
2328

2429
// MetricsWriterList represent a list of MetricsWriter
@@ -82,20 +87,50 @@ func (m MetricsWriter) WriteAll(w io.Writer) error {
8287
return nil
8388
}
8489

85-
// SanitizeHeaders removes duplicate headers from the given MetricsWriterList for the same family (generated through CRS).
86-
// These are expected to be consecutive since G** resolution generates groups of similar metrics with same headers before moving onto the next G** spec in the CRS configuration.
87-
func SanitizeHeaders(writers MetricsWriterList) MetricsWriterList {
90+
// SanitizeHeaders sanitizes the headers of the given MetricsWriterList.
91+
func SanitizeHeaders(contentType string, writers MetricsWriterList) MetricsWriterList {
8892
var lastHeader string
8993
for _, writer := range writers {
9094
if len(writer.stores) > 0 {
91-
for i, header := range writer.stores[0].headers {
95+
for i := 0; i < len(writer.stores[0].headers); {
96+
header := writer.stores[0].headers[i]
97+
98+
// Removes duplicate headers from the given MetricsWriterList for the same family (generated through CRS).
99+
// These are expected to be consecutive since G** resolution generates groups of similar metrics with same headers before moving onto the next G** spec in the CRS configuration.
100+
// Skip this step if we encounter a repeated header, as it will be removed.
101+
if header != lastHeader && strings.HasPrefix(header, "# HELP") {
102+
103+
// If the requested content type was proto-based (such as FmtProtoDelim, FmtProtoText, or FmtProtoCompact), replace "info" and "statesets" with "gauge", as they are not recognized by Prometheus' protobuf machinery.
104+
if strings.HasPrefix(contentType, expfmt.ProtoType) {
105+
infoTypeString := string(metric.Info)
106+
stateSetTypeString := string(metric.StateSet)
107+
if strings.HasSuffix(header, infoTypeString) {
108+
header = header[:len(header)-len(infoTypeString)] + string(metric.Gauge)
109+
writer.stores[0].headers[i] = header
110+
}
111+
if strings.HasSuffix(header, stateSetTypeString) {
112+
header = header[:len(header)-len(stateSetTypeString)] + string(metric.Gauge)
113+
writer.stores[0].headers[i] = header
114+
}
115+
}
116+
}
117+
118+
// Nullify duplicate headers after the sanitization to not miss out on any new candidates.
92119
if header == lastHeader {
93-
writer.stores[0].headers[i] = ""
94-
} else {
95-
lastHeader = header
120+
writer.stores[0].headers = append(writer.stores[0].headers[:i], writer.stores[0].headers[i+1:]...)
121+
122+
// Do not increment the index, as the next header is now at the current index.
123+
continue
96124
}
125+
126+
// Update the last header.
127+
lastHeader = header
128+
129+
// Move to the next header.
130+
i++
97131
}
98132
}
99133
}
134+
100135
return writers
101136
}

0 commit comments

Comments
 (0)