From 1ab6445f97747b20437f2a3d2e6734398e1e8320 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Oct 2026 18:57:47 +0000 Subject: [PATCH] chore: bump github.com/prometheus/common from 0.71.0 to 0.72.0 Bumps [github.com/prometheus/common](https://github.com/prometheus/common) from 0.71.0 to 0.72.0. - [Release notes](https://github.com/prometheus/common/releases) - [Changelog](https://github.com/prometheus/common/blob/main/CHANGELOG.md) - [Commits](https://github.com/prometheus/common/compare/v0.71.0...v0.72.0) --- updated-dependencies: - dependency-name: github.com/prometheus/common dependency-version: 0.72.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 +- go.sum | 8 +- .../prometheus/common/expfmt/encode.go | 46 +- .../prometheus/common/expfmt/expfmt.go | 113 +-- .../common/expfmt/openmetrics_2_0_create.go | 844 +++++++++++++++++- .../prometheus/common/expfmt/text_parse.go | 8 +- .../prometheus/common/model/value.go | 22 - .../prometheus/common/model/value_float127.go | 70 ++ .../common/model/value_histogram127.go | 95 ++ vendor/modules.txt | 8 +- 10 files changed, 1097 insertions(+), 121 deletions(-) create mode 100644 vendor/github.com/prometheus/common/model/value_float127.go create mode 100644 vendor/github.com/prometheus/common/model/value_histogram127.go diff --git a/go.mod b/go.mod index 92402b4580..bd6ca31155 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ require ( github.com/kylelemons/godebug v1.1.0 github.com/open-policy-agent/cert-controller v0.16.0 github.com/prometheus/client_golang v1.24.1 - github.com/prometheus/common v0.71.0 + github.com/prometheus/common v0.72.0 github.com/spf13/cobra v1.10.2 github.com/spyzhov/ajson v0.9.6 github.com/stretchr/testify v1.12.1 @@ -141,7 +141,7 @@ require ( github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/client_model v0.6.3 // indirect github.com/prometheus/procfs v0.21.1 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect diff --git a/go.sum b/go.sum index b726e523b0..3a47fd848b 100644 --- a/go.sum +++ b/go.sum @@ -298,10 +298,10 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH github.com/prometheus/client_golang v1.24.1 h1:JnJkREXzWxUdCuPFpIWZiPispT9xVV59uiuyR2bPlnU= github.com/prometheus/client_golang v1.24.1/go.mod h1:F+oSRECHg4sse5ucfYpYDeIv/hu68Zo0uoHKetWnzcE= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= -github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.71.0 h1:9KDAKb7Mj3HEVKyFCK6Dc/HIwlBzZIN2l7/lrHl3KK8= -github.com/prometheus/common v0.71.0/go.mod h1:CLJ5H8TEsGX8bl31BdMkfhIZ+QmZ9tBPPotUxUbfcmk= +github.com/prometheus/client_model v0.6.3 h1:O0jaTVAYNxTHYInEPFJt5I3+sN8zqBtVMPTB1qyxiEo= +github.com/prometheus/client_model v0.6.3/go.mod h1:gpN5P9S7Rr6Yr92PiQ+Ixvhf6JZEkF1dnxsYL2aPBEM= +github.com/prometheus/common v0.72.0 h1:tAYsE+sPJxIncDAobm4H5aQjmox9ZxEIIqPbiffa8G4= +github.com/prometheus/common v0.72.0/go.mod h1:77NWqAQ2tXT7BIK40qjJdw5Acrsrg1TlHAnsQi3i6mk= github.com/prometheus/procfs v0.21.1 h1:GljZCt+zSTS+NZq88cyQ1LjZ+RCHp3uVuabBWA5+OJI= github.com/prometheus/procfs v0.21.1/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= diff --git a/vendor/github.com/prometheus/common/expfmt/encode.go b/vendor/github.com/prometheus/common/expfmt/encode.go index 6945356ca5..651e69a2c8 100644 --- a/vendor/github.com/prometheus/common/expfmt/encode.go +++ b/vendor/github.com/prometheus/common/expfmt/encode.go @@ -18,6 +18,7 @@ import ( "io" "mime" "net/http" + "slices" "github.com/munnerz/goautoneg" dto "github.com/prometheus/client_model/go" @@ -36,7 +37,7 @@ func init() { FmtProtoText, FmtProtoCompact, FmtOpenMetrics_1_0_0, - fmtOpenMetrics_2_0_0, + FmtOpenMetrics_2_0_0, FmtOpenMetrics_0_0_1, } { if parsed := goautoneg.ParseAccept(string(f)); len(parsed) > 0 { @@ -45,6 +46,37 @@ func init() { } } +var ( + defaultAcceptedFormats = []Format{ + FmtProtoDelim, + FmtProtoText, + FmtProtoCompact, + FmtText, + } + + defaultOpenMetricsAcceptedFormats = []Format{ + FmtOpenMetrics_1_0_0, + FmtOpenMetrics_0_0_1, + FmtProtoDelim, + FmtProtoText, + FmtProtoCompact, + FmtText, + } +) + +// DefaultAcceptedFormats returns a copy of standard accepted formats for Negotiate, +// ordered by preference (delimited protobuf, protobuf text, compact protobuf text, +// and Prometheus text format). +func DefaultAcceptedFormats() []Format { + return slices.Clone(defaultAcceptedFormats) +} + +// DefaultOpenMetricsAcceptedFormats returns a copy of standard accepted formats including +// stable OpenMetrics formats, ordered by preference. +func DefaultOpenMetricsAcceptedFormats() []Format { + return slices.Clone(defaultOpenMetricsAcceptedFormats) +} + // Encoder types encode metric families into an underlying wire protocol. type Encoder interface { Encode(*dto.MetricFamily) error @@ -78,19 +110,19 @@ func (ec encoderCloser) Close() error { // appropriate accepted type is found, FmtText is returned (which is the // Prometheus text format). // -// Deprecated: Use NegotiateAccept(h, FmtProtoDelim, FmtProtoText, FmtProtoCompact, FmtText) +// Deprecated: Use NegotiateAccept(h, DefaultAcceptedFormats()...) // or specify only the formats supported by your server. func Negotiate(h http.Header) Format { - return NegotiateAccept(h, FmtProtoDelim, FmtProtoText, FmtProtoCompact, FmtText) + return NegotiateAccept(h, defaultAcceptedFormats...) } -// NegotiateIncludingOpenMetrics works like Negotiate but includes -// FmtOpenMetrics as an option for the result. +// NegotiateIncludingOpenMetrics works like Negotiate but includes all stable +// FmtOpenMetrics formats as an option for the result. // -// Deprecated: Use NegotiateAccept(h, FmtOpenMetrics_1_0_0, FmtOpenMetrics_0_0_1, FmtProtoDelim, FmtProtoText, FmtProtoCompact, FmtText) +// Deprecated: Use NegotiateAccept(h, DefaultOpenMetricsAcceptedFormats()...) // or specify only the formats supported by your server. func NegotiateIncludingOpenMetrics(h http.Header) Format { - return NegotiateAccept(h, FmtOpenMetrics_1_0_0, FmtOpenMetrics_0_0_1, FmtProtoDelim, FmtProtoText, FmtProtoCompact, FmtText) + return NegotiateAccept(h, defaultOpenMetricsAcceptedFormats...) } // NegotiateAccept returns the Content-Type based on the given Accept header diff --git a/vendor/github.com/prometheus/common/expfmt/expfmt.go b/vendor/github.com/prometheus/common/expfmt/expfmt.go index 40035f2013..ea9a0a2a04 100644 --- a/vendor/github.com/prometheus/common/expfmt/expfmt.go +++ b/vendor/github.com/prometheus/common/expfmt/expfmt.go @@ -16,27 +16,20 @@ package expfmt import ( "errors" + "mime" "strings" "github.com/prometheus/common/model" ) -// Format specifies the HTTP content type of the different wire protocols. -type Format string - // Constants to assemble the Content-Type values for the different wire -// protocols. The Content-Type strings here are all for the legacy exposition -// formats, where valid characters for metric names and label names are limited. -// Support for arbitrary UTF-8 characters in those names is already partially -// implemented in this module (see model.ValidationScheme), but to actually use -// it on the wire, new content-type strings will have to be agreed upon and -// added here. +// protocols. const ( TextVersion = "0.0.4" ProtoType = `application/vnd.google.protobuf` ProtoProtocol = `io.prometheus.client.MetricFamily` - // Deprecated: Use expfmt.NewFormat(expfmt.TypeProtoCompact) instead. - ProtoFmt = ProtoType + "; proto=" + ProtoProtocol + ";" + ProtoFmt = ProtoType + "; proto=" + ProtoProtocol + ";" + OpenMetricsType = `application/openmetrics-text` //nolint:revive // Allow for underscores. OpenMetricsVersion_0_0_1 = "0.0.1" @@ -44,26 +37,27 @@ const ( OpenMetricsVersion_1_0_0 = "1.0.0" //nolint:revive // Allow for underscores. OpenMetricsVersion_2_0_0 = "2.0.0" +) + +// Format specifies the HTTP content type of the different wire protocols. +// The Content-Type values for the different wire protocols represent +// baseline Content-Types used for HTTP headers and content negotiation. +// +// Because Content-Types on the wire may include dynamic parameters +// (such as "; escaping=...") or whitespace variations, avoid comparing Format +// values using direct equality (==). +type Format string - // The Content-Type values for the different wire protocols. Do not do direct - // comparisons to these constants, instead use the comparison functions. - // - // Deprecated: Use expfmt.NewFormat(expfmt.TypeUnknown) instead. - FmtUnknown Format = `` - // Deprecated: Use expfmt.NewFormat(expfmt.TypeTextPlain) instead. - FmtText Format = `text/plain; version=` + TextVersion + `; charset=utf-8` - // Deprecated: Use expfmt.NewFormat(expfmt.TypeProtoDelim) instead. - FmtProtoDelim Format = ProtoFmt + ` encoding=delimited` - // Deprecated: Use expfmt.NewFormat(expfmt.TypeProtoText) instead. - FmtProtoText Format = ProtoFmt + ` encoding=text` - // Deprecated: Use expfmt.NewFormat(expfmt.TypeProtoCompact) instead. +const ( + FmtUnknown Format = `` + FmtText Format = `text/plain; version=` + TextVersion + `; charset=utf-8` + FmtProtoDelim Format = ProtoFmt + ` encoding=delimited` + FmtProtoText Format = ProtoFmt + ` encoding=text` FmtProtoCompact Format = ProtoFmt + ` encoding=compact-text` - // Deprecated: Use expfmt.NewFormat(expfmt.TypeOpenMetrics) instead. //nolint:revive // Allow for underscores. - FmtOpenMetrics_1_0_0 Format = OpenMetricsType + `; version=` + OpenMetricsVersion_1_0_0 + `; charset=utf-8` + FmtOpenMetrics_2_0_0 Format = OpenMetricsType + `; version=` + OpenMetricsVersion_2_0_0 + `; charset=utf-8` //nolint:revive // Allow for underscores. - fmtOpenMetrics_2_0_0 Format = OpenMetricsType + `; version=` + OpenMetricsVersion_2_0_0 + `; charset=utf-8` - // Deprecated: Use expfmt.NewFormat(expfmt.TypeOpenMetrics) instead. + FmtOpenMetrics_1_0_0 Format = OpenMetricsType + `; version=` + OpenMetricsVersion_1_0_0 + `; charset=utf-8` //nolint:revive // Allow for underscores. FmtOpenMetrics_0_0_1 Format = OpenMetricsType + `; version=` + OpenMetricsVersion_0_0_1 + `; charset=utf-8` ) @@ -90,7 +84,7 @@ const ( // NewFormat generates a new Format from the type provided. Mostly used for // tests, most Formats should be generated as part of content negotiation in -// encode.go. If a type has more than one version, the latest version will be +// encode.go. If a type has more than one version, the latest stable version will be // returned. func NewFormat(t FormatType) Format { switch t { @@ -112,8 +106,8 @@ func NewFormat(t FormatType) Format { // NewOpenMetricsFormat generates a new OpenMetrics format matching the // specified version number. // -// Note: OpenMetrics version 2.0.0 is experimental and encode-only (currently -// supporting counter, gauge, and untyped metric types). +// Note: OpenMetrics version 2.0.0 is experimental and encode-only (supporting +// counter, gauge, summary, untyped, histogram, and gaugehistogram metric types). func NewOpenMetricsFormat(version string) (Format, error) { if version == OpenMetricsVersion_0_0_1 { return FmtOpenMetrics_0_0_1, nil @@ -122,33 +116,21 @@ func NewOpenMetricsFormat(version string) (Format, error) { return FmtOpenMetrics_1_0_0, nil } if version == OpenMetricsVersion_2_0_0 { - // OpenMetrics 2.0.0 is experimental and encode-only (counter/gauge/untyped). - return fmtOpenMetrics_2_0_0, nil + // OpenMetrics 2.0.0 is experimental and encode-only. + return FmtOpenMetrics_2_0_0, nil } return FmtUnknown, errors.New("unknown open metrics version string") } -// WithEscapingScheme returns a copy of Format with the specified escaping -// scheme appended to the end. If an escaping scheme already exists it is -// removed. -func (f Format) WithEscapingScheme(s model.EscapingScheme) Format { - var terms []string - for p := range strings.SplitSeq(string(f), ";") { - toks := strings.Split(p, "=") - if len(toks) != 2 { - trimmed := strings.TrimSpace(p) - if len(trimmed) > 0 { - terms = append(terms, trimmed) - } - continue - } - key := strings.TrimSpace(toks[0]) - if key != model.EscapingKey { - terms = append(terms, strings.TrimSpace(p)) - } +// Version returns the version parameter without validating whether it is supported. +// It returns an empty string if the parameter is absent or the Content-Type +// cannot be parsed. +func (f Format) Version() string { + _, params, err := mime.ParseMediaType(string(f)) + if err != nil { + return "" } - terms = append(terms, model.EscapingKey+"="+s.String()) - return Format(strings.Join(terms, "; ")) + return params["version"] } // FormatType deduces an overall FormatType for the given format. @@ -200,8 +182,31 @@ func (f Format) FormatType() FormatType { } } -// ToEscapingScheme returns an EscapingScheme depending on the Format. Iff the -// Format contains a escaping=allow-utf-8 term, it will select NoEscaping. If a valid +// WithEscapingScheme returns a copy of Format with the specified escaping +// scheme appended to the end. If an escaping scheme already exists it is +// removed. +func (f Format) WithEscapingScheme(s model.EscapingScheme) Format { + var terms []string + for p := range strings.SplitSeq(string(f), ";") { + toks := strings.Split(p, "=") + if len(toks) != 2 { + trimmed := strings.TrimSpace(p) + if len(trimmed) > 0 { + terms = append(terms, trimmed) + } + continue + } + key := strings.TrimSpace(toks[0]) + if key != model.EscapingKey { + terms = append(terms, strings.TrimSpace(p)) + } + } + terms = append(terms, model.EscapingKey+"="+s.String()) + return Format(strings.Join(terms, "; ")) +} + +// ToEscapingScheme returns an EscapingScheme depending on the Format. IFF the +// Format contains an escaping=allow-utf-8 term, it will select NoEscaping. If a valid // "escaping" term exists, that will be used. Otherwise, the global default will // be returned. func (f Format) ToEscapingScheme() model.EscapingScheme { diff --git a/vendor/github.com/prometheus/common/expfmt/openmetrics_2_0_create.go b/vendor/github.com/prometheus/common/expfmt/openmetrics_2_0_create.go index 8ca9b6be76..600ac32f5c 100644 --- a/vendor/github.com/prometheus/common/expfmt/openmetrics_2_0_create.go +++ b/vendor/github.com/prometheus/common/expfmt/openmetrics_2_0_create.go @@ -21,6 +21,7 @@ import ( "math" "strconv" "strings" + "unicode/utf8" dto "github.com/prometheus/client_model/go" "google.golang.org/protobuf/types/known/timestamppb" @@ -31,11 +32,16 @@ import ( // It returns the number of bytes written and any error encountered. // // NOTE: This method targets OpenMetrics 2.0.0 (currently aligned with 2.0-rc.0) which is experimental and -// encode-only (currently supporting counter, gauge, and untyped metric types). -// Breaking changes might happen in the future. This implementation is still a -// work-in-progress, and does not yet support all features of the format. +// encode-only. All metric types defined in client_model (counter, gauge, summary, untyped, histogram, +// and gaugehistogram) are supported; info and stateset are not supported as they are not present in +// client_model. Breaking changes might happen in the future. // EncoderOptions are accepted for signature compatibility with // MetricFamilyToOpenMetrics and are currently ignored. +// +// OpenMetrics 2.0 enforces stricter validation rules defined in the specification +// than Prometheus text or OpenMetrics 1.0 formats (such as requiring non-negative +// count and sum, and non-negative quantile values for summaries). Consequently, +// MetricFamilyToOpenMetrics20 may reject metric families that the other encoders accept. func MetricFamilyToOpenMetrics20(out io.Writer, in *dto.MetricFamily, options ...EncoderOption) (written int, err error) { // Options are accepted for signature compatibility and ignored. _ = options @@ -46,8 +52,26 @@ func MetricFamilyToOpenMetrics20(out io.Writer, in *dto.MetricFamily, options .. if containsRawNewline(name) { return 0, fmt.Errorf("MetricFamily name %q contains raw newlines", name) } - if in.Unit != nil && containsRawNewline(*in.Unit) { - return 0, fmt.Errorf("MetricFamily unit %q contains raw newlines", *in.Unit) + if !utf8.ValidString(name) { + return 0, fmt.Errorf("MetricFamily name %q is not valid UTF-8", name) + } + if in.Help != nil { + if !utf8.ValidString(*in.Help) { + return 0, fmt.Errorf("MetricFamily help %q is not valid UTF-8", *in.Help) + } + // A carriage return may appear in HELP, but not at the end, where it + // would make the line end in "\r\n". + if strings.HasSuffix(*in.Help, "\r") { + return 0, fmt.Errorf("MetricFamily help %q ends with a carriage return", *in.Help) + } + } + if in.Unit != nil { + if containsRawNewline(*in.Unit) { + return 0, fmt.Errorf("MetricFamily unit %q contains raw newlines", *in.Unit) + } + if !utf8.ValidString(*in.Unit) { + return 0, fmt.Errorf("MetricFamily unit %q is not valid UTF-8", *in.Unit) + } } // Try the interface upgrade. If it doesn't work, we'll use a @@ -340,21 +364,773 @@ func writeOpenMetrics20Timestamp(w enhancedWriter, f float64) (int, error) { return written, err } -// Stubs for Summary and Histogram - func writeCompositeSummary(w enhancedWriter, name string, metric *dto.Metric) (int, error) { - _ = w - _ = name - _ = metric - return 0, errors.New("summary not implemented yet") + s := metric.Summary + if s == nil { + return 0, fmt.Errorf("expected summary in metric %s", name) + } + + if err := validateLabels20(metric.Label); err != nil { + return 0, err + } + for _, lp := range metric.Label { + if lp.GetName() == "quantile" { + return 0, fmt.Errorf("metric %s is a summary but label set contains %q label", name, "quantile") + } + } + + sum := s.GetSampleSum() + if math.IsNaN(sum) { + return 0, fmt.Errorf("summary sum cannot be NaN in metric %s", name) + } + if sum < 0 { + return 0, fmt.Errorf("summary sum cannot be negative (%g) in metric %s", sum, name) + } + + var prevQuantile float64 + for i, q := range s.Quantile { + if q == nil { + return 0, fmt.Errorf("expected non-nil quantile in metric %s", name) + } + qv := q.GetQuantile() + if math.IsNaN(qv) { + return 0, fmt.Errorf("summary quantile cannot be NaN in metric %s", name) + } + if qv < 0 || qv > 1 { + return 0, fmt.Errorf("summary quantile %g must be between 0 and 1 in metric %s", qv, name) + } + if i > 0 && qv <= prevQuantile { + return 0, fmt.Errorf("summary quantiles must be strictly increasing: %g <= %g in metric %s", qv, prevQuantile, name) + } + prevQuantile = qv + + v := q.GetValue() + if !math.IsNaN(v) && v < 0 { + return 0, fmt.Errorf("summary quantile value cannot be negative (%g) in metric %s", v, name) + } + } + + if s.CreatedTimestamp != nil { + if err := s.CreatedTimestamp.CheckValid(); err != nil { + return 0, fmt.Errorf("invalid created timestamp in metric %s: %w", name, err) + } + } + + written := 0 + n, err := writeOpenMetricsNameAndLabelPairs(w, name, metric.Label, "", 0) + written += n + if err != nil { + return written, err + } + + n, err = w.WriteString(" {count:") + written += n + if err != nil { + return written, err + } + + n, err = writeUint(w, s.GetSampleCount()) + written += n + if err != nil { + return written, err + } + + n, err = w.WriteString(",sum:") + written += n + if err != nil { + return written, err + } + + n, err = writeFloat(w, sum) + written += n + if err != nil { + return written, err + } + + n, err = w.WriteString(",quantile:[") + written += n + if err != nil { + return written, err + } + + for i, q := range s.Quantile { + if i > 0 { + err = w.WriteByte(',') + written++ + if err != nil { + return written, err + } + } + n, err = writeFloat(w, q.GetQuantile()) + written += n + if err != nil { + return written, err + } + err = w.WriteByte(':') + written++ + if err != nil { + return written, err + } + n, err = writeFloat(w, q.GetValue()) + written += n + if err != nil { + return written, err + } + } + + n, err = w.WriteString("]}") + written += n + if err != nil { + return written, err + } + + if metric.TimestampMs != nil { + err = w.WriteByte(' ') + written++ + if err != nil { + return written, err + } + n, err = writeOpenMetrics20Timestamp(w, float64(*metric.TimestampMs)/1000) + written += n + if err != nil { + return written, err + } + } + + if s.CreatedTimestamp != nil { + n, err = w.WriteString(" st@") + written += n + if err != nil { + return written, err + } + n, err = writeProtoTimestamp(w, s.CreatedTimestamp) + written += n + if err != nil { + return written, err + } + } + + err = w.WriteByte('\n') + written++ + if err != nil { + return written, err + } + + return written, nil } func writeCompositeHistogram(w enhancedWriter, name string, metric *dto.Metric, isGauge bool) (int, error) { - _ = w - _ = name - _ = metric - _ = isGauge - return 0, errors.New("histogram not implemented yet") + h := metric.Histogram + if h == nil { + return 0, fmt.Errorf("expected histogram in metric %s", name) + } + + isNative := h.Schema != nil + hasClassicBuckets := len(h.Bucket) > 0 || !isNative + + if err := validateLabels20(metric.Label); err != nil { + return 0, err + } + if hasClassicBuckets { + for _, lp := range metric.Label { + if lp.GetName() == "le" { + return 0, fmt.Errorf("metric %s has classic buckets but label set contains %q label", name, "le") + } + } + } + + if h.SampleCountFloat != nil { + c := *h.SampleCountFloat + if math.IsNaN(c) { + if isGauge { + return 0, fmt.Errorf("gaugehistogram count cannot be NaN in metric %s", name) + } + return 0, fmt.Errorf("histogram count cannot be NaN in metric %s", name) + } + if !isGauge && c < 0 { + return 0, fmt.Errorf("histogram count cannot be negative (%g) in metric %s", c, name) + } + } + + var isFloatCount bool + var sampleCountFloat float64 + var sampleCountUint uint64 + switch { + case h.SampleCountFloat != nil && (*h.SampleCountFloat > 0 || h.SampleCount == nil || isGauge): + isFloatCount = true + sampleCountFloat = *h.SampleCountFloat + case h.SampleCount != nil: + sampleCountUint = *h.SampleCount + sampleCountFloat = float64(sampleCountUint) + } + + if isNative { + if err := validateNativeHistogram(name, h, isGauge); err != nil { + return 0, err + } + } + + if hasClassicBuckets { + if err := validateClassicBuckets(name, h, sampleCountFloat, sampleCountUint, isFloatCount, isGauge); err != nil { + return 0, err + } + } + + if !isGauge && h.CreatedTimestamp != nil { + if err := h.CreatedTimestamp.CheckValid(); err != nil { + return 0, fmt.Errorf("invalid created timestamp in metric %s: %w", name, err) + } + } + + written := 0 + n, err := writeOpenMetricsNameAndLabelPairs(w, name, metric.Label, "", 0) + written += n + if err != nil { + return written, err + } + + n, err = w.WriteString(" {") + written += n + if err != nil { + return written, err + } + + if isGauge { + n, err = w.WriteString("gcount:") + } else { + n, err = w.WriteString("count:") + } + written += n + if err != nil { + return written, err + } + + if isFloatCount { + n, err = writeFloat(w, sampleCountFloat) + } else { + n, err = writeUint(w, sampleCountUint) + } + written += n + if err != nil { + return written, err + } + + if isGauge { + n, err = w.WriteString(",gsum:") + } else { + n, err = w.WriteString(",sum:") + } + written += n + if err != nil { + return written, err + } + n, err = writeFloat(w, h.GetSampleSum()) + written += n + if err != nil { + return written, err + } + + if isNative { + n, err = writeNativeBuckets(w, h, isGauge) + written += n + if err != nil { + return written, err + } + } + + var classicExemplars []*dto.Exemplar + if hasClassicBuckets { + n, err = writeClassicBuckets(w, h, sampleCountFloat, sampleCountUint, isFloatCount, isGauge, &classicExemplars) + written += n + if err != nil { + return written, err + } + } + + err = w.WriteByte('}') + written++ + if err != nil { + return written, err + } + + if metric.TimestampMs != nil { + err = w.WriteByte(' ') + written++ + if err != nil { + return written, err + } + n, err = writeOpenMetrics20Timestamp(w, float64(*metric.TimestampMs)/1000) + written += n + if err != nil { + return written, err + } + } + + if !isGauge && h.CreatedTimestamp != nil { + ts := h.CreatedTimestamp + n, err = w.WriteString(" st@") + written += n + if err != nil { + return written, err + } + n, err = writeProtoTimestamp(w, ts) + written += n + if err != nil { + return written, err + } + } + + var exemplarsToEmit []*dto.Exemplar + if len(classicExemplars) > 0 { + // We follow the spec suggestion and prefer classic histogram + // exemplars in dual mode. + exemplarsToEmit = classicExemplars + } else if len(h.Exemplars) > 0 { + exemplarsToEmit = h.Exemplars + } + + for _, e := range exemplarsToEmit { + if e == nil || e.Timestamp == nil { + continue + } + n, err = writeExemplar20(w, e) + written += n + if err != nil { + return written, err + } + } + + err = w.WriteByte('\n') + written++ + if err != nil { + return written, err + } + + return written, nil +} + +func validateNativeHistogram(name string, h *dto.Histogram, isGauge bool) error { + schema := *h.Schema + if schema < -4 || schema > 8 { + return fmt.Errorf("native histogram schema %d is out of range [-4, 8] in metric %s", schema, name) + } + + zeroThreshold := h.GetZeroThreshold() + if math.IsNaN(zeroThreshold) || math.IsInf(zeroThreshold, 0) || zeroThreshold < 0 { + return fmt.Errorf("native histogram zero_threshold %g must be a non-negative, finite number in metric %s", zeroThreshold, name) + } + + if h.ZeroCountFloat != nil { + c := *h.ZeroCountFloat + if math.IsNaN(c) { + return fmt.Errorf("native histogram zero_count cannot be NaN in metric %s", name) + } + if !isGauge && c < 0 { + return fmt.Errorf("native histogram zero_count cannot be negative (%g) in metric %s", c, name) + } + } + + if err := validateSpansAndBuckets(name, "negative", h.NegativeSpan, h.NegativeDelta, h.NegativeCount, isGauge); err != nil { + return err + } + return validateSpansAndBuckets(name, "positive", h.PositiveSpan, h.PositiveDelta, h.PositiveCount, isGauge) +} + +func validateSpansAndBuckets( + name string, + spanName string, + spans []*dto.BucketSpan, + deltas []int64, + floatCounts []float64, + isGauge bool, +) error { + isFloatBuckets := len(floatCounts) > 0 + var numBuckets int + if isFloatBuckets { + numBuckets = len(floatCounts) + } else { + numBuckets = len(deltas) + } + + var totalLength uint64 + for i, span := range spans { + if span == nil { + return errors.New("expected non-nil bucket span") + } + if i > 0 && span.GetOffset() < 0 { + return fmt.Errorf("subsequent %s span offset cannot be negative: %d in metric %s", spanName, span.GetOffset(), name) + } + totalLength += uint64(span.GetLength()) + } + + if numBuckets == 0 { + if totalLength == 0 { + return nil + } + return fmt.Errorf("sum of %s span lengths (%d) does not match bucket count (0) in metric %s", spanName, totalLength, name) + } + + if totalLength != uint64(numBuckets) { + return fmt.Errorf("sum of %s span lengths (%d) does not match bucket count (%d) in metric %s", spanName, totalLength, numBuckets, name) + } + + if isFloatBuckets { + for _, v := range floatCounts { + if math.IsNaN(v) { + return fmt.Errorf("%s bucket count cannot be NaN in metric %s", spanName, name) + } + if !isGauge && v < 0 { + return fmt.Errorf("%s bucket count cannot be negative (%g) in metric %s", spanName, v, name) + } + } + } else { + var current int64 + for _, d := range deltas { + current += d + if !isGauge && current < 0 { + return fmt.Errorf("%s bucket count cannot be negative (%d) in metric %s", spanName, current, name) + } + } + } + return nil +} + +func writeNativeBuckets(w enhancedWriter, h *dto.Histogram, isGauge bool) (int, error) { + schema := *h.Schema + zeroThreshold := h.GetZeroThreshold() + + var isFloatZeroCount bool + var zeroCountFloat float64 + var zeroCountUint uint64 + switch { + case h.ZeroCountFloat != nil && (*h.ZeroCountFloat > 0 || h.ZeroCount == nil || isGauge): + isFloatZeroCount = true + zeroCountFloat = *h.ZeroCountFloat + case h.ZeroCount != nil: + zeroCountUint = *h.ZeroCount + zeroCountFloat = float64(zeroCountUint) + } + + written := 0 + n, err := w.WriteString(",schema:") + written += n + if err != nil { + return written, err + } + n, err = writeInt(w, int64(schema)) + written += n + if err != nil { + return written, err + } + + n, err = w.WriteString(",zero_threshold:") + written += n + if err != nil { + return written, err + } + n, err = writeFloat(w, zeroThreshold) + written += n + if err != nil { + return written, err + } + + n, err = w.WriteString(",zero_count:") + written += n + if err != nil { + return written, err + } + if isFloatZeroCount { + n, err = writeFloat(w, zeroCountFloat) + } else { + n, err = writeUint(w, zeroCountUint) + } + written += n + if err != nil { + return written, err + } + + n, err = writeSpansAndBuckets(w, ",negative_spans:[", ",negative_buckets:[", h.NegativeSpan, h.NegativeDelta, h.NegativeCount) + written += n + if err != nil { + return written, err + } + + n, err = writeSpansAndBuckets(w, ",positive_spans:[", ",positive_buckets:[", h.PositiveSpan, h.PositiveDelta, h.PositiveCount) + written += n + if err != nil { + return written, err + } + + return written, nil +} + +func writeSpansAndBuckets( + w enhancedWriter, + spansPrefix string, + bucketsPrefix string, + spans []*dto.BucketSpan, + deltas []int64, + floatCounts []float64, +) (int, error) { + isFloatBuckets := len(floatCounts) > 0 + var numBuckets int + if isFloatBuckets { + numBuckets = len(floatCounts) + } else { + numBuckets = len(deltas) + } + + if numBuckets == 0 { + return 0, nil + } + + written := 0 + n, err := w.WriteString(spansPrefix) + written += n + if err != nil { + return written, err + } + for i, span := range spans { + if i > 0 { + err = w.WriteByte(',') + written++ + if err != nil { + return written, err + } + } + n, err = writeInt(w, int64(span.GetOffset())) + written += n + if err != nil { + return written, err + } + err = w.WriteByte(':') + written++ + if err != nil { + return written, err + } + n, err = writeUint(w, uint64(span.GetLength())) + written += n + if err != nil { + return written, err + } + } + err = w.WriteByte(']') + written++ + if err != nil { + return written, err + } + + n, err = w.WriteString(bucketsPrefix) + written += n + if err != nil { + return written, err + } + if isFloatBuckets { + for i, v := range floatCounts { + if i > 0 { + err = w.WriteByte(',') + written++ + if err != nil { + return written, err + } + } + n, err = writeFloat(w, v) + written += n + if err != nil { + return written, err + } + } + } else { + var current int64 + for i, d := range deltas { + if i > 0 { + err = w.WriteByte(',') + written++ + if err != nil { + return written, err + } + } + current += d + n, err = writeInt(w, current) + written += n + if err != nil { + return written, err + } + } + } + err = w.WriteByte(']') + written++ + if err != nil { + return written, err + } + + return written, nil +} + +func validateClassicBuckets( + name string, + h *dto.Histogram, + sampleCount float64, + sampleCountUint uint64, + isFloatCount bool, + isGauge bool, +) error { + var infSeen bool + var prevBound float64 + var prevCount float64 + for i, b := range h.Bucket { + if b == nil { + return errors.New("expected non-nil bucket") + } + ub := b.GetUpperBound() + if math.IsNaN(ub) { + return fmt.Errorf("classic bucket upper bound cannot be NaN in metric %s", name) + } + if i > 0 && ub <= prevBound { + return fmt.Errorf("classic bucket upper bounds must be strictly increasing: %g <= %g in metric %s", ub, prevBound, name) + } + prevBound = ub + + if b.CumulativeCountFloat != nil { + c := *b.CumulativeCountFloat + if math.IsNaN(c) { + return fmt.Errorf("classic bucket count cannot be NaN in metric %s", name) + } + if !isGauge && c < 0 { + return fmt.Errorf("classic bucket count cannot be negative (%g) in metric %s", c, name) + } + } + + var bCount float64 + switch { + case b.CumulativeCountFloat != nil && (*b.CumulativeCountFloat > 0 || b.CumulativeCount == nil || isGauge): + bCount = *b.CumulativeCountFloat + case b.CumulativeCount != nil: + bCount = float64(*b.CumulativeCount) + } + + if !isGauge && i > 0 && bCount < prevCount { + return fmt.Errorf("classic bucket counts must be monotonically increasing: %g < %g in metric %s", bCount, prevCount, name) + } + prevCount = bCount + + if math.IsInf(ub, +1) { + if i != len(h.Bucket)-1 { + return fmt.Errorf("+Inf bucket must be the last bucket in metric %s", name) + } + infSeen = true + if isFloatCount { + if bCount != sampleCount { + return fmt.Errorf("classic bucket +Inf count (%g) does not match sample count (%g) in metric %s", bCount, sampleCount, name) + } + } else { + if b.CumulativeCount != nil { + if *b.CumulativeCount != sampleCountUint { + return fmt.Errorf("classic bucket +Inf count (%d) does not match sample count (%d) in metric %s", *b.CumulativeCount, sampleCountUint, name) + } + } else { + if bCount < 0 || bCount > math.MaxUint64 || uint64(bCount) != sampleCountUint || float64(uint64(bCount)) != bCount { + return fmt.Errorf("classic bucket +Inf count (%g) does not match sample count (%d) in metric %s", bCount, sampleCountUint, name) + } + } + } + } + } + + if !infSeen && len(h.Bucket) > 0 && !isGauge { + if sampleCount < prevCount { + return fmt.Errorf("sample count (%g) is less than highest bucket count (%g) in metric %s", sampleCount, prevCount, name) + } + } + return nil +} + +func writeClassicBuckets( + w enhancedWriter, + h *dto.Histogram, + sampleCount float64, + sampleCountUint uint64, + isFloatCount bool, + isGauge bool, + collectedExemplars *[]*dto.Exemplar, +) (int, error) { + var infSeen bool + written := 0 + n, err := w.WriteString(",bucket:[") + written += n + if err != nil { + return written, err + } + + for i, b := range h.Bucket { + if i > 0 { + err = w.WriteByte(',') + written++ + if err != nil { + return written, err + } + } + n, err = writeFloat(w, b.GetUpperBound()) + written += n + if err != nil { + return written, err + } + err = w.WriteByte(':') + written++ + if err != nil { + return written, err + } + switch { + case b.CumulativeCountFloat != nil && (*b.CumulativeCountFloat > 0 || b.CumulativeCount == nil || isGauge): + n, err = writeFloat(w, *b.CumulativeCountFloat) + case b.CumulativeCount != nil: + n, err = writeUint(w, *b.CumulativeCount) + default: + n, err = writeUint(w, 0) + } + written += n + if err != nil { + return written, err + } + if math.IsInf(b.GetUpperBound(), +1) { + infSeen = true + } + if b.Exemplar != nil && b.Exemplar.Timestamp != nil { + *collectedExemplars = append(*collectedExemplars, b.Exemplar) + } + } + + if !infSeen { + if len(h.Bucket) > 0 { + err = w.WriteByte(',') + written++ + if err != nil { + return written, err + } + } + n, err = w.WriteString("+Inf:") + written += n + if err != nil { + return written, err + } + if isFloatCount { + n, err = writeFloat(w, sampleCount) + } else { + n, err = writeUint(w, sampleCountUint) + } + written += n + if err != nil { + return written, err + } + } + + err = w.WriteByte(']') + written++ + if err != nil { + return written, err + } + + return written, nil } func validateLabels20(labels []*dto.LabelPair) error { @@ -391,20 +1167,40 @@ func writeProtoTimestamp(w enhancedWriter, ts *timestamppb.Timestamp) (int, erro if err := ts.CheckValid(); err != nil { return 0, err } - n, err := writeInt(w, ts.Seconds) + if ts.Nanos == 0 { + return writeInt(w, ts.Seconds) + } + sec := ts.Seconds + nanos := int64(ts.Nanos) + if sec < 0 { + sec++ + nanos = 1_000_000_000 - nanos + if sec == 0 { + n, err := w.WriteString("-0") + if err != nil { + return n, err + } + n2, err := writeNanos(w, nanos) + return n + n2, err + } + } + + n, err := writeInt(w, sec) if err != nil { return n, err } - if ts.Nanos == 0 { - return n, nil - } - err = w.WriteByte('.') - written := n + 1 + n2, err := writeNanos(w, nanos) + return n + n2, err +} + +func writeNanos(w enhancedWriter, nanos int64) (int, error) { + err := w.WriteByte('.') if err != nil { - return written, err + return 0, err } + written := 1 bp := numBufPool.Get().(*[]byte) - *bp = strconv.AppendInt((*bp)[:0], int64(ts.Nanos), 10) + *bp = strconv.AppendInt((*bp)[:0], nanos, 10) pad := 9 - len(*bp) for range pad { err = w.WriteByte('0') diff --git a/vendor/github.com/prometheus/common/expfmt/text_parse.go b/vendor/github.com/prometheus/common/expfmt/text_parse.go index 4ce1f40b81..10529300f4 100644 --- a/vendor/github.com/prometheus/common/expfmt/text_parse.go +++ b/vendor/github.com/prometheus/common/expfmt/text_parse.go @@ -194,6 +194,7 @@ func (p *TextParser) reset(in io.Reader) { // start of a line (or whitespace leading up to it). func (p *TextParser) startOfLine() stateFn { p.lineCount++ + p.currentMetric = nil p.currentMetricIsInsideBraces = false p.currentMetricInsideBracesIsPresent = false if p.skipBlankTab(); p.err != nil { @@ -339,12 +340,11 @@ func (p *TextParser) startLabelName() stateFn { return nil // Unexpected end of input. } if p.currentByte == '}' { - if p.currentMF == nil { + if p.currentMetric == nil { // The closing brace was reached before any metric name was read, // e.g. for the input "{}". There is no metric to attach labels to, // so this is a malformed exposition. This mirrors the guard in - // startLabelValue. currentMF (not currentMetric) is checked because - // reset only clears currentMF between parses. + // startLabelValue. currentMetric is cleared at the start of each line. p.parseError("invalid metric name") p.currentLabelPairs = nil return nil @@ -495,7 +495,7 @@ func (p *TextParser) startLabelValue() stateFn { return p.startLabelName case '}': - if p.currentMF == nil { + if p.currentMetric == nil { p.parseError("invalid metric name") return nil } diff --git a/vendor/github.com/prometheus/common/model/value.go b/vendor/github.com/prometheus/common/model/value.go index 8dffd9c4a5..eed5c9ef3b 100644 --- a/vendor/github.com/prometheus/common/model/value.go +++ b/vendor/github.com/prometheus/common/model/value.go @@ -224,28 +224,6 @@ func (ss SampleStream) MarshalJSON() ([]byte, error) { } } -func (ss *SampleStream) UnmarshalJSON(b []byte) error { - v := struct { - Metric Metric `json:"metric"` - Values []SamplePair `json:"values"` - Histograms []SampleHistogramPair `json:"histograms"` - }{ - Metric: ss.Metric, - Values: ss.Values, - Histograms: ss.Histograms, - } - - if err := json.Unmarshal(b, &v); err != nil { - return err - } - - ss.Metric = v.Metric - ss.Values = v.Values - ss.Histograms = v.Histograms - - return nil -} - // Scalar is a scalar value evaluated at the set timestamp. type Scalar struct { Value SampleValue `json:"value"` diff --git a/vendor/github.com/prometheus/common/model/value_float127.go b/vendor/github.com/prometheus/common/model/value_float127.go new file mode 100644 index 0000000000..821cc1e725 --- /dev/null +++ b/vendor/github.com/prometheus/common/model/value_float127.go @@ -0,0 +1,70 @@ +// Copyright The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build go1.27 + +package model + +import ( + "bytes" + "encoding/json/jsontext" + "errors" +) + +var nullBytes = []byte("null") + +func (s *SamplePair) UnmarshalJSONFrom(dec *jsontext.Decoder) error { + if t, err := dec.ReadToken(); err != nil { + return err + } else if t.Kind() == jsontext.KindNull { + return nil + } else if t.Kind() != jsontext.KindBeginArray { + return errors.New("expected [") + } + + // Loop until we see the end of the array (tolerate arrays of any length). + for i := 0; dec.PeekKind() != jsontext.KindEndArray; i++ { + switch i { + case 0: + if v, err := dec.ReadValue(); err != nil { + return err + } else if bytes.Equal(v, nullBytes) { + // Skip null values to match SamplePair#UnmarshalJSON behavior. + } else if err := s.Timestamp.UnmarshalJSON(v); err != nil { + return err + } + case 1: + if v, err := dec.ReadValue(); err != nil { + return err + } else if bytes.Equal(v, nullBytes) { + // Skip null values to match SamplePair#UnmarshalJSON behavior. + } else if err := s.Value.UnmarshalJSON(v); err != nil { + return err + } + default: + // Skip any remaining array items to match UnmarshalJSON behavior. + if err := dec.SkipValue(); err != nil { + return err + } + } + } + + // Read the final end array token. + if t, err := dec.ReadToken(); err != nil { + return err + } else if t.Kind() != jsontext.KindEndArray { + return errors.New("expected ]") + } + + return nil +} diff --git a/vendor/github.com/prometheus/common/model/value_histogram127.go b/vendor/github.com/prometheus/common/model/value_histogram127.go new file mode 100644 index 0000000000..fbcfb3f94f --- /dev/null +++ b/vendor/github.com/prometheus/common/model/value_histogram127.go @@ -0,0 +1,95 @@ +// Copyright The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build go1.27 + +package model + +import ( + "bytes" + "encoding/json/jsontext" + "encoding/json/v2" + "errors" +) + +func (s *SampleHistogramPair) UnmarshalJSONFrom(dec *jsontext.Decoder) error { + if t, err := dec.ReadToken(); err != nil { + return err + } else if t.Kind() != jsontext.KindBeginArray { + return errors.New("expected [") + } + + if v, err := dec.ReadValue(); err != nil { + return err + } else if bytes.Equal(v, nullBytes) { + // Skip null values to match SamplePair#UnmarshalJSON behavior. + } else if err := s.Timestamp.UnmarshalJSON(v); err != nil { + return err + } + + if err := json.UnmarshalDecode(dec, &s.Histogram); err != nil { + return err + } else if s.Histogram == nil { + return errors.New("histogram is nil") + } + + if t, err := dec.ReadToken(); err != nil { + return err + } else if t.Kind() != jsontext.KindEndArray { + return errors.New("expected ]") + } + return nil +} + +func (s *HistogramBucket) UnmarshalJSONFrom(dec *jsontext.Decoder) error { + if t, err := dec.ReadToken(); err != nil { + return err + } else if t.Kind() != jsontext.KindBeginArray { + return errors.New("expected [") + } + + if err := json.UnmarshalDecode(dec, &s.Boundaries); err != nil { + return err + } + + if v, err := dec.ReadValue(); err != nil { + return err + } else if bytes.Equal(v, nullBytes) { + // Skip null values to match SamplePair#UnmarshalJSON behavior. + } else if err := s.Lower.UnmarshalJSON(v); err != nil { + return err + } + + if v, err := dec.ReadValue(); err != nil { + return err + } else if bytes.Equal(v, nullBytes) { + // Skip null values to match SamplePair#UnmarshalJSON behavior. + } else if err := s.Upper.UnmarshalJSON(v); err != nil { + return err + } + + if v, err := dec.ReadValue(); err != nil { + return err + } else if bytes.Equal(v, nullBytes) { + // Skip null values to match SamplePair#UnmarshalJSON behavior. + } else if err := s.Count.UnmarshalJSON(v); err != nil { + return err + } + + if t, err := dec.ReadToken(); err != nil { + return err + } else if t.Kind() != jsontext.KindEndArray { + return errors.New("expected ]") + } + return nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index a59e82da40..9a3e7d6387 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -449,11 +449,11 @@ github.com/prometheus/client_golang/prometheus/collectors github.com/prometheus/client_golang/prometheus/internal github.com/prometheus/client_golang/prometheus/promhttp github.com/prometheus/client_golang/prometheus/promhttp/internal -# github.com/prometheus/client_model v0.6.2 -## explicit; go 1.22.0 +# github.com/prometheus/client_model v0.6.3 +## explicit; go 1.23 github.com/prometheus/client_model/go -# github.com/prometheus/common v0.71.0 -## explicit; go 1.25.0 +# github.com/prometheus/common v0.72.0 +## explicit; go 1.26.0 github.com/prometheus/common/expfmt github.com/prometheus/common/model # github.com/prometheus/procfs v0.21.1