From b98b5dbab4491ca6fa8186cdf4569fc85ed91f62 Mon Sep 17 00:00:00 2001 From: Pranshu Srivastava Date: Wed, 13 Dec 2023 12:37:50 +0530 Subject: [PATCH] fallback to `gauge` for `protofmt`-based negotiations Fallback to `gauge` metric type when the negotiated content-type is `protofmt`-based, since Prometheus' protobuf machinery does not recognize all OpenMetrics types (`info` and `statesets` in this context). Fixes: #2248 Signed-off-by: Pranshu Srivastava --- pkg/metrics_store/metrics_writer.go | 18 +++++++++++++++--- pkg/metricshandler/metrics_handler.go | 3 ++- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/pkg/metrics_store/metrics_writer.go b/pkg/metrics_store/metrics_writer.go index 77a9df6106..f7a31faa1c 100644 --- a/pkg/metrics_store/metrics_writer.go +++ b/pkg/metrics_store/metrics_writer.go @@ -18,7 +18,10 @@ package metricsstore import ( "fmt" + "github.com/prometheus/common/expfmt" "io" + "k8s.io/kube-state-metrics/v2/pkg/metric" + "strings" ) // MetricsWriterList represent a list of MetricsWriter @@ -82,13 +85,22 @@ func (m MetricsWriter) WriteAll(w io.Writer) error { return nil } -// SanitizeHeaders removes duplicate headers from the given MetricsWriterList for the same family (generated through CRS). -// 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. -func SanitizeHeaders(writers MetricsWriterList) MetricsWriterList { +// SanitizeHeaders sanitizes the headers of the given MetricsWriterList. +func SanitizeHeaders(contentType string, writers MetricsWriterList) MetricsWriterList { var lastHeader string for _, writer := range writers { if len(writer.stores) > 0 { for i, header := range writer.stores[0].headers { + // If the requested content type was proto-based, replace the type with "gauge", as "info" and "statesets" are not recognized by Prometheus' protobuf machinery. + if strings.HasPrefix(contentType, expfmt.ProtoFmt) && + strings.HasPrefix(header, "# TYPE") && + (strings.HasSuffix(header, " "+string(metric.Info)+"\n") || strings.HasSuffix(header, " "+string(metric.StateSet)+"\n")) { + typeStringWithoutTypePaddedIndex := strings.LastIndex(header, " ") + typeStringWithoutType := header[:typeStringWithoutTypePaddedIndex] + writer.stores[0].headers[i] = typeStringWithoutType + string(metric.Gauge) + } + // Removes duplicate headers from the given MetricsWriterList for the same family (generated through CRS). + // 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. if header == lastHeader { writer.stores[0].headers[i] = "" } else { diff --git a/pkg/metricshandler/metrics_handler.go b/pkg/metricshandler/metrics_handler.go index 911d24b161..4f2ddb530b 100644 --- a/pkg/metricshandler/metrics_handler.go +++ b/pkg/metricshandler/metrics_handler.go @@ -187,6 +187,7 @@ func (m *MetricsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { var writer io.Writer = w contentType := expfmt.NegotiateIncludingOpenMetrics(r.Header) + gotContentType := contentType // We do not support protobuf at the moment. Fall back to FmtText if the negotiated exposition format is not FmtOpenMetrics See: https://github.com/kubernetes/kube-state-metrics/issues/2022 if contentType != expfmt.FmtOpenMetrics_1_0_0 && contentType != expfmt.FmtOpenMetrics_0_0_1 { @@ -208,7 +209,7 @@ func (m *MetricsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } } - m.metricsWriters = metricsstore.SanitizeHeaders(m.metricsWriters) + m.metricsWriters = metricsstore.SanitizeHeaders(string(gotContentType), m.metricsWriters) for _, w := range m.metricsWriters { err := w.WriteAll(writer) if err != nil {