diff options
Diffstat (limited to 'vendor/github.com/prometheus')
14 files changed, 773 insertions, 147 deletions
| diff --git a/vendor/github.com/prometheus/common/expfmt/decode.go b/vendor/github.com/prometheus/common/expfmt/decode.go index 0ca86a3dc..b2b89b017 100644 --- a/vendor/github.com/prometheus/common/expfmt/decode.go +++ b/vendor/github.com/prometheus/common/expfmt/decode.go @@ -14,6 +14,7 @@  package expfmt  import ( +	"bufio"  	"fmt"  	"io"  	"math" @@ -21,8 +22,8 @@ import (  	"net/http"  	dto "github.com/prometheus/client_model/go" +	"google.golang.org/protobuf/encoding/protodelim" -	"github.com/matttproud/golang_protobuf_extensions/v2/pbutil"  	"github.com/prometheus/common/model"  ) @@ -44,7 +45,7 @@ func ResponseFormat(h http.Header) Format {  	mediatype, params, err := mime.ParseMediaType(ct)  	if err != nil { -		return FmtUnknown +		return fmtUnknown  	}  	const textType = "text/plain" @@ -52,28 +53,28 @@ func ResponseFormat(h http.Header) Format {  	switch mediatype {  	case ProtoType:  		if p, ok := params["proto"]; ok && p != ProtoProtocol { -			return FmtUnknown +			return fmtUnknown  		}  		if e, ok := params["encoding"]; ok && e != "delimited" { -			return FmtUnknown +			return fmtUnknown  		} -		return FmtProtoDelim +		return fmtProtoDelim  	case textType:  		if v, ok := params["version"]; ok && v != TextVersion { -			return FmtUnknown +			return fmtUnknown  		} -		return FmtText +		return fmtText  	} -	return FmtUnknown +	return fmtUnknown  }  // NewDecoder returns a new decoder based on the given input format.  // If the input format does not imply otherwise, a text format decoder is returned.  func NewDecoder(r io.Reader, format Format) Decoder { -	switch format { -	case FmtProtoDelim: +	switch format.FormatType() { +	case TypeProtoDelim:  		return &protoDecoder{r: r}  	}  	return &textDecoder{r: r} @@ -86,8 +87,10 @@ type protoDecoder struct {  // Decode implements the Decoder interface.  func (d *protoDecoder) Decode(v *dto.MetricFamily) error { -	_, err := pbutil.ReadDelimited(d.r, v) -	if err != nil { +	opts := protodelim.UnmarshalOptions{ +		MaxSize: -1, +	} +	if err := opts.UnmarshalFrom(bufio.NewReader(d.r), v); err != nil {  		return err  	}  	if !model.IsValidMetricName(model.LabelValue(v.GetName())) { diff --git a/vendor/github.com/prometheus/common/expfmt/encode.go b/vendor/github.com/prometheus/common/expfmt/encode.go index ca2140600..8fd806184 100644 --- a/vendor/github.com/prometheus/common/expfmt/encode.go +++ b/vendor/github.com/prometheus/common/expfmt/encode.go @@ -18,10 +18,12 @@ import (  	"io"  	"net/http" -	"github.com/matttproud/golang_protobuf_extensions/v2/pbutil" -	"github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg" +	"google.golang.org/protobuf/encoding/protodelim"  	"google.golang.org/protobuf/encoding/prototext" +	"github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg" +	"github.com/prometheus/common/model" +  	dto "github.com/prometheus/client_model/go"  ) @@ -60,23 +62,32 @@ func (ec encoderCloser) Close() error {  // as the support is still experimental. To include the option to negotiate  // FmtOpenMetrics, use NegotiateOpenMetrics.  func Negotiate(h http.Header) Format { +	escapingScheme := Format(fmt.Sprintf("; escaping=%s", Format(model.NameEscapingScheme.String())))  	for _, ac := range goautoneg.ParseAccept(h.Get(hdrAccept)) { +		if escapeParam := ac.Params[model.EscapingKey]; escapeParam != "" { +			switch Format(escapeParam) { +			case model.AllowUTF8, model.EscapeUnderscores, model.EscapeDots, model.EscapeValues: +				escapingScheme = Format(fmt.Sprintf("; escaping=%s", escapeParam)) +			default: +				// If the escaping parameter is unknown, ignore it. +			} +		}  		ver := ac.Params["version"]  		if ac.Type+"/"+ac.SubType == ProtoType && ac.Params["proto"] == ProtoProtocol {  			switch ac.Params["encoding"] {  			case "delimited": -				return FmtProtoDelim +				return fmtProtoDelim + escapingScheme  			case "text": -				return FmtProtoText +				return fmtProtoText + escapingScheme  			case "compact-text": -				return FmtProtoCompact +				return fmtProtoCompact + escapingScheme  			}  		}  		if ac.Type == "text" && ac.SubType == "plain" && (ver == TextVersion || ver == "") { -			return FmtText +			return fmtText + escapingScheme  		}  	} -	return FmtText +	return fmtText + escapingScheme  }  // NegotiateIncludingOpenMetrics works like Negotiate but includes @@ -84,29 +95,40 @@ func Negotiate(h http.Header) Format {  // temporary and will disappear once FmtOpenMetrics is fully supported and as  // such may be negotiated by the normal Negotiate function.  func NegotiateIncludingOpenMetrics(h http.Header) Format { +	escapingScheme := Format(fmt.Sprintf("; escaping=%s", Format(model.NameEscapingScheme.String())))  	for _, ac := range goautoneg.ParseAccept(h.Get(hdrAccept)) { +		if escapeParam := ac.Params[model.EscapingKey]; escapeParam != "" { +			switch Format(escapeParam) { +			case model.AllowUTF8, model.EscapeUnderscores, model.EscapeDots, model.EscapeValues: +				escapingScheme = Format(fmt.Sprintf("; escaping=%s", escapeParam)) +			default: +				// If the escaping parameter is unknown, ignore it. +			} +		}  		ver := ac.Params["version"]  		if ac.Type+"/"+ac.SubType == ProtoType && ac.Params["proto"] == ProtoProtocol {  			switch ac.Params["encoding"] {  			case "delimited": -				return FmtProtoDelim +				return fmtProtoDelim + escapingScheme  			case "text": -				return FmtProtoText +				return fmtProtoText + escapingScheme  			case "compact-text": -				return FmtProtoCompact +				return fmtProtoCompact + escapingScheme  			}  		}  		if ac.Type == "text" && ac.SubType == "plain" && (ver == TextVersion || ver == "") { -			return FmtText +			return fmtText + escapingScheme  		}  		if ac.Type+"/"+ac.SubType == OpenMetricsType && (ver == OpenMetricsVersion_0_0_1 || ver == OpenMetricsVersion_1_0_0 || ver == "") { -			if ver == OpenMetricsVersion_1_0_0 { -				return FmtOpenMetrics_1_0_0 +			switch ver { +			case OpenMetricsVersion_1_0_0: +				return fmtOpenMetrics_1_0_0 + escapingScheme +			default: +				return fmtOpenMetrics_0_0_1 + escapingScheme  			} -			return FmtOpenMetrics_0_0_1  		}  	} -	return FmtText +	return fmtText + escapingScheme  }  // NewEncoder returns a new encoder based on content type negotiation. All @@ -115,44 +137,48 @@ func NegotiateIncludingOpenMetrics(h http.Header) Format {  // for FmtOpenMetrics, but a future (breaking) release will add the Close method  // to the Encoder interface directly. The current version of the Encoder  // interface is kept for backwards compatibility. +// In cases where the Format does not allow for UTF-8 names, the global +// NameEscapingScheme will be applied.  func NewEncoder(w io.Writer, format Format) Encoder { -	switch format { -	case FmtProtoDelim: +	escapingScheme := format.ToEscapingScheme() + +	switch format.FormatType() { +	case TypeProtoDelim:  		return encoderCloser{  			encode: func(v *dto.MetricFamily) error { -				_, err := pbutil.WriteDelimited(w, v) +				_, err := protodelim.MarshalTo(w, v)  				return err  			},  			close: func() error { return nil },  		} -	case FmtProtoCompact: +	case TypeProtoCompact:  		return encoderCloser{  			encode: func(v *dto.MetricFamily) error { -				_, err := fmt.Fprintln(w, v.String()) +				_, err := fmt.Fprintln(w, model.EscapeMetricFamily(v, escapingScheme).String())  				return err  			},  			close: func() error { return nil },  		} -	case FmtProtoText: +	case TypeProtoText:  		return encoderCloser{  			encode: func(v *dto.MetricFamily) error { -				_, err := fmt.Fprintln(w, prototext.Format(v)) +				_, err := fmt.Fprintln(w, prototext.Format(model.EscapeMetricFamily(v, escapingScheme)))  				return err  			},  			close: func() error { return nil },  		} -	case FmtText: +	case TypeTextPlain:  		return encoderCloser{  			encode: func(v *dto.MetricFamily) error { -				_, err := MetricFamilyToText(w, v) +				_, err := MetricFamilyToText(w, model.EscapeMetricFamily(v, escapingScheme))  				return err  			},  			close: func() error { return nil },  		} -	case FmtOpenMetrics_0_0_1, FmtOpenMetrics_1_0_0: +	case TypeOpenMetrics:  		return encoderCloser{  			encode: func(v *dto.MetricFamily) error { -				_, err := MetricFamilyToOpenMetrics(w, v) +				_, err := MetricFamilyToOpenMetrics(w, model.EscapeMetricFamily(v, escapingScheme))  				return err  			},  			close: func() error { diff --git a/vendor/github.com/prometheus/common/expfmt/expfmt.go b/vendor/github.com/prometheus/common/expfmt/expfmt.go index c4cb20f0d..6fc9555e3 100644 --- a/vendor/github.com/prometheus/common/expfmt/expfmt.go +++ b/vendor/github.com/prometheus/common/expfmt/expfmt.go @@ -14,30 +14,154 @@  // Package expfmt contains tools for reading and writing Prometheus metrics.  package expfmt +import ( +	"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. +// 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.  const (  	TextVersion              = "0.0.4"  	ProtoType                = `application/vnd.google.protobuf`  	ProtoProtocol            = `io.prometheus.client.MetricFamily` -	ProtoFmt                 = ProtoType + "; proto=" + ProtoProtocol + ";" +	protoFmt                 = ProtoType + "; proto=" + ProtoProtocol + ";"  	OpenMetricsType          = `application/openmetrics-text`  	OpenMetricsVersion_0_0_1 = "0.0.1"  	OpenMetricsVersion_1_0_0 = "1.0.0" -	// The Content-Type values for the different wire protocols. -	FmtUnknown           Format = `<unknown>` -	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` -	FmtOpenMetrics_1_0_0 Format = OpenMetricsType + `; version=` + OpenMetricsVersion_1_0_0 + `; charset=utf-8` -	FmtOpenMetrics_0_0_1 Format = OpenMetricsType + `; version=` + OpenMetricsVersion_0_0_1 + `; charset=utf-8` +	// The Content-Type values for the different wire protocols. Note that these +	// values are now unexported. If code was relying on comparisons to these +	// constants, instead use FormatType(). +	fmtUnknown           Format = `<unknown>` +	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` +	fmtOpenMetrics_1_0_0 Format = OpenMetricsType + `; version=` + OpenMetricsVersion_1_0_0 + `; charset=utf-8` +	fmtOpenMetrics_0_0_1 Format = OpenMetricsType + `; version=` + OpenMetricsVersion_0_0_1 + `; charset=utf-8`  )  const (  	hdrContentType = "Content-Type"  	hdrAccept      = "Accept"  ) + +// FormatType is a Go enum representing the overall category for the given +// Format. As the number of Format permutations increases, doing basic string +// comparisons are not feasible, so this enum captures the most useful +// high-level attribute of the Format string. +type FormatType int + +const ( +	TypeUnknown = iota +	TypeProtoCompact +	TypeProtoDelim +	TypeProtoText +	TypeTextPlain +	TypeOpenMetrics +) + +// 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. +func NewFormat(t FormatType) Format { +	switch t { +	case TypeProtoCompact: +		return fmtProtoCompact +	case TypeProtoDelim: +		return fmtProtoDelim +	case TypeProtoText: +		return fmtProtoText +	case TypeTextPlain: +		return fmtText +	case TypeOpenMetrics: +		return fmtOpenMetrics_1_0_0 +	default: +		return fmtUnknown +	} +} + +// FormatType deduces an overall FormatType for the given format. +func (f Format) FormatType() FormatType { +	toks := strings.Split(string(f), ";") +	if len(toks) < 2 { +		return TypeUnknown +	} + +	params := make(map[string]string) +	for i, t := range toks { +		if i == 0 { +			continue +		} +		args := strings.Split(t, "=") +		if len(args) != 2 { +			continue +		} +		params[strings.TrimSpace(args[0])] = strings.TrimSpace(args[1]) +	} + +	switch strings.TrimSpace(toks[0]) { +	case ProtoType: +		if params["proto"] != ProtoProtocol { +			return TypeUnknown +		} +		switch params["encoding"] { +		case "delimited": +			return TypeProtoDelim +		case "text": +			return TypeProtoText +		case "compact-text": +			return TypeProtoCompact +		default: +			return TypeUnknown +		} +	case OpenMetricsType: +		if params["charset"] != "utf-8" { +			return TypeUnknown +		} +		return TypeOpenMetrics +	case "text/plain": +		v, ok := params["version"] +		if !ok { +			return TypeTextPlain +		} +		if v == TextVersion { +			return TypeTextPlain +		} +		return TypeUnknown +	default: +		return TypeUnknown +	} +} + +// 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 +// "escaping" term exists, that will be used. Otherwise, the global default will +// be returned. +func (format Format) ToEscapingScheme() model.EscapingScheme { +	for _, p := range strings.Split(string(format), ";") { +		toks := strings.Split(p, "=") +		if len(toks) != 2 { +			continue +		} +		key, value := strings.TrimSpace(toks[0]), strings.TrimSpace(toks[1]) +		if key == model.EscapingKey { +			scheme, err := model.ToEscapingScheme(value) +			if err != nil { +				return model.NameEscapingScheme +			} +			return scheme +		} +	} +	return model.NameEscapingScheme +} diff --git a/vendor/github.com/prometheus/common/expfmt/openmetrics_create.go b/vendor/github.com/prometheus/common/expfmt/openmetrics_create.go index 21cdddcf0..5622578ed 100644 --- a/vendor/github.com/prometheus/common/expfmt/openmetrics_create.go +++ b/vendor/github.com/prometheus/common/expfmt/openmetrics_create.go @@ -35,6 +35,18 @@ import (  // sanity checks. If the input contains duplicate metrics or invalid metric or  // label names, the conversion will result in invalid text format output.  // +// If metric names conform to the legacy validation pattern, they will be placed +// outside the brackets in the traditional way, like `foo{}`. If the metric name +// fails the legacy validation check, it will be placed quoted inside the +// brackets: `{"foo"}`. As stated above, the input is assumed to be santized and +// no error will be thrown in this case. +// +// Similar to metric names, if label names conform to the legacy validation +// pattern, they will be unquoted as normal, like `foo{bar="baz"}`. If the label +// name fails the legacy validation check, it will be quoted: +// `foo{"bar"="baz"}`. As stated above, the input is assumed to be santized and +// no error will be thrown in this case. +//  // This function fulfills the type 'expfmt.encoder'.  //  // Note that OpenMetrics requires a final `# EOF` line. Since this function acts @@ -98,7 +110,7 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int  		if err != nil {  			return  		} -		n, err = w.WriteString(shortName) +		n, err = writeName(w, shortName)  		written += n  		if err != nil {  			return @@ -124,7 +136,7 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int  	if err != nil {  		return  	} -	n, err = w.WriteString(shortName) +	n, err = writeName(w, shortName)  	written += n  	if err != nil {  		return @@ -303,21 +315,9 @@ func writeOpenMetricsSample(  	floatValue float64, intValue uint64, useIntValue bool,  	exemplar *dto.Exemplar,  ) (int, error) { -	var written int -	n, err := w.WriteString(name) -	written += n -	if err != nil { -		return written, err -	} -	if suffix != "" { -		n, err = w.WriteString(suffix) -		written += n -		if err != nil { -			return written, err -		} -	} -	n, err = writeOpenMetricsLabelPairs( -		w, metric.Label, additionalLabelName, additionalLabelValue, +	written := 0 +	n, err := writeOpenMetricsNameAndLabelPairs( +		w, name+suffix, metric.Label, additionalLabelName, additionalLabelValue,  	)  	written += n  	if err != nil { @@ -365,27 +365,58 @@ func writeOpenMetricsSample(  	return written, nil  } -// writeOpenMetricsLabelPairs works like writeOpenMetrics but formats the float -// in OpenMetrics style. -func writeOpenMetricsLabelPairs( +// writeOpenMetricsNameAndLabelPairs works like writeOpenMetricsSample but +// formats the float in OpenMetrics style. +func writeOpenMetricsNameAndLabelPairs(  	w enhancedWriter, +	name string,  	in []*dto.LabelPair,  	additionalLabelName string, additionalLabelValue float64,  ) (int, error) { -	if len(in) == 0 && additionalLabelName == "" { -		return 0, nil -	}  	var ( -		written   int -		separator byte = '{' +		written            int +		separator          byte = '{' +		metricInsideBraces      = false  	) + +	if name != "" { +		// If the name does not pass the legacy validity check, we must put the +		// metric name inside the braces, quoted. +		if !model.IsValidLegacyMetricName(model.LabelValue(name)) { +			metricInsideBraces = true +			err := w.WriteByte(separator) +			written++ +			if err != nil { +				return written, err +			} +			separator = ',' +		} + +		n, err := writeName(w, name) +		written += n +		if err != nil { +			return written, err +		} +	} + +	if len(in) == 0 && additionalLabelName == "" { +		if metricInsideBraces { +			err := w.WriteByte('}') +			written++ +			if err != nil { +				return written, err +			} +		} +		return written, nil +	} +  	for _, lp := range in {  		err := w.WriteByte(separator)  		written++  		if err != nil {  			return written, err  		} -		n, err := w.WriteString(lp.GetName()) +		n, err := writeName(w, lp.GetName())  		written += n  		if err != nil {  			return written, err @@ -451,7 +482,7 @@ func writeExemplar(w enhancedWriter, e *dto.Exemplar) (int, error) {  	if err != nil {  		return written, err  	} -	n, err = writeOpenMetricsLabelPairs(w, e.Label, "", 0) +	n, err = writeOpenMetricsNameAndLabelPairs(w, "", e.Label, "", 0)  	written += n  	if err != nil {  		return written, err diff --git a/vendor/github.com/prometheus/common/expfmt/text_create.go b/vendor/github.com/prometheus/common/expfmt/text_create.go index 2946b8f1a..f9b8265a9 100644 --- a/vendor/github.com/prometheus/common/expfmt/text_create.go +++ b/vendor/github.com/prometheus/common/expfmt/text_create.go @@ -62,6 +62,18 @@ var (  // contains duplicate metrics or invalid metric or label names, the conversion  // will result in invalid text format output.  // +// If metric names conform to the legacy validation pattern, they will be placed +// outside the brackets in the traditional way, like `foo{}`. If the metric name +// fails the legacy validation check, it will be placed quoted inside the +// brackets: `{"foo"}`. As stated above, the input is assumed to be santized and +// no error will be thrown in this case. +// +// Similar to metric names, if label names conform to the legacy validation +// pattern, they will be unquoted as normal, like `foo{bar="baz"}`. If the label +// name fails the legacy validation check, it will be quoted: +// `foo{"bar"="baz"}`. As stated above, the input is assumed to be santized and +// no error will be thrown in this case. +//  // This method fulfills the type 'prometheus.encoder'.  func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err error) {  	// Fail-fast checks. @@ -98,7 +110,7 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err e  		if err != nil {  			return  		} -		n, err = w.WriteString(name) +		n, err = writeName(w, name)  		written += n  		if err != nil {  			return @@ -124,7 +136,7 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err e  	if err != nil {  		return  	} -	n, err = w.WriteString(name) +	n, err = writeName(w, name)  	written += n  	if err != nil {  		return @@ -280,21 +292,9 @@ func writeSample(  	additionalLabelName string, additionalLabelValue float64,  	value float64,  ) (int, error) { -	var written int -	n, err := w.WriteString(name) -	written += n -	if err != nil { -		return written, err -	} -	if suffix != "" { -		n, err = w.WriteString(suffix) -		written += n -		if err != nil { -			return written, err -		} -	} -	n, err = writeLabelPairs( -		w, metric.Label, additionalLabelName, additionalLabelValue, +	written := 0 +	n, err := writeNameAndLabelPairs( +		w, name+suffix, metric.Label, additionalLabelName, additionalLabelValue,  	)  	written += n  	if err != nil { @@ -330,32 +330,64 @@ func writeSample(  	return written, nil  } -// writeLabelPairs converts a slice of LabelPair proto messages plus the -// explicitly given additional label pair into text formatted as required by the -// text format and writes it to 'w'. An empty slice in combination with an empty -// string 'additionalLabelName' results in nothing being written. Otherwise, the -// label pairs are written, escaped as required by the text format, and enclosed -// in '{...}'. The function returns the number of bytes written and any error -// encountered. -func writeLabelPairs( +// writeNameAndLabelPairs converts a slice of LabelPair proto messages plus the +// explicitly given metric name and additional label pair into text formatted as +// required by the text format and writes it to 'w'. An empty slice in +// combination with an empty string 'additionalLabelName' results in nothing +// being written. Otherwise, the label pairs are written, escaped as required by +// the text format, and enclosed in '{...}'. The function returns the number of +// bytes written and any error encountered. If the metric name is not +// legacy-valid, it will be put inside the brackets as well. Legacy-invalid +// label names will also be quoted. +func writeNameAndLabelPairs(  	w enhancedWriter, +	name string,  	in []*dto.LabelPair,  	additionalLabelName string, additionalLabelValue float64,  ) (int, error) { -	if len(in) == 0 && additionalLabelName == "" { -		return 0, nil -	}  	var ( -		written   int -		separator byte = '{' +		written            int +		separator          byte = '{' +		metricInsideBraces      = false  	) + +	if name != "" { +		// If the name does not pass the legacy validity check, we must put the +		// metric name inside the braces. +		if !model.IsValidLegacyMetricName(model.LabelValue(name)) { +			metricInsideBraces = true +			err := w.WriteByte(separator) +			written++ +			if err != nil { +				return written, err +			} +			separator = ',' +		} +		n, err := writeName(w, name) +		written += n +		if err != nil { +			return written, err +		} +	} + +	if len(in) == 0 && additionalLabelName == "" { +		if metricInsideBraces { +			err := w.WriteByte('}') +			written++ +			if err != nil { +				return written, err +			} +		} +		return written, nil +	} +  	for _, lp := range in {  		err := w.WriteByte(separator)  		written++  		if err != nil {  			return written, err  		} -		n, err := w.WriteString(lp.GetName()) +		n, err := writeName(w, lp.GetName())  		written += n  		if err != nil {  			return written, err @@ -462,3 +494,27 @@ func writeInt(w enhancedWriter, i int64) (int, error) {  	numBufPool.Put(bp)  	return written, err  } + +// writeName writes a string as-is if it complies with the legacy naming +// scheme, or escapes it in double quotes if not. +func writeName(w enhancedWriter, name string) (int, error) { +	if model.IsValidLegacyMetricName(model.LabelValue(name)) { +		return w.WriteString(name) +	} +	var written int +	var err error +	err = w.WriteByte('"') +	written++ +	if err != nil { +		return written, err +	} +	var n int +	n, err = writeEscapedString(w, name, true) +	written += n +	if err != nil { +		return written, err +	} +	err = w.WriteByte('"') +	written++ +	return written, err +} diff --git a/vendor/github.com/prometheus/common/expfmt/text_parse.go b/vendor/github.com/prometheus/common/expfmt/text_parse.go index 35db1cc9d..26490211a 100644 --- a/vendor/github.com/prometheus/common/expfmt/text_parse.go +++ b/vendor/github.com/prometheus/common/expfmt/text_parse.go @@ -16,6 +16,7 @@ package expfmt  import (  	"bufio"  	"bytes" +	"errors"  	"fmt"  	"io"  	"math" @@ -24,8 +25,9 @@ import (  	dto "github.com/prometheus/client_model/go" -	"github.com/prometheus/common/model"  	"google.golang.org/protobuf/proto" + +	"github.com/prometheus/common/model"  )  // A stateFn is a function that represents a state in a state machine. By @@ -112,7 +114,7 @@ func (p *TextParser) TextToMetricFamilies(in io.Reader) (map[string]*dto.MetricF  	// stream. Turn this error into something nicer and more  	// meaningful. (io.EOF is often used as a signal for the legitimate end  	// of an input stream.) -	if p.err == io.EOF { +	if p.err != nil && errors.Is(p.err, io.EOF) {  		p.parseError("unexpected end of input stream")  	}  	return p.metricFamiliesByName, p.err @@ -146,7 +148,7 @@ func (p *TextParser) startOfLine() stateFn {  		// which is not an error but the signal that we are done.  		// Any other error that happens to align with the start of  		// a line is still an error. -		if p.err == io.EOF { +		if errors.Is(p.err, io.EOF) {  			p.err = nil  		}  		return nil diff --git a/vendor/github.com/prometheus/common/model/alert.go b/vendor/github.com/prometheus/common/model/alert.go index 35e739c7a..178fdbaf6 100644 --- a/vendor/github.com/prometheus/common/model/alert.go +++ b/vendor/github.com/prometheus/common/model/alert.go @@ -90,13 +90,13 @@ func (a *Alert) Validate() error {  		return fmt.Errorf("start time must be before end time")  	}  	if err := a.Labels.Validate(); err != nil { -		return fmt.Errorf("invalid label set: %s", err) +		return fmt.Errorf("invalid label set: %w", err)  	}  	if len(a.Labels) == 0 {  		return fmt.Errorf("at least one label pair required")  	}  	if err := a.Annotations.Validate(); err != nil { -		return fmt.Errorf("invalid annotations: %s", err) +		return fmt.Errorf("invalid annotations: %w", err)  	}  	return nil  } diff --git a/vendor/github.com/prometheus/common/model/labels.go b/vendor/github.com/prometheus/common/model/labels.go index ef8956335..3317ce22f 100644 --- a/vendor/github.com/prometheus/common/model/labels.go +++ b/vendor/github.com/prometheus/common/model/labels.go @@ -97,17 +97,25 @@ var LabelNameRE = regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9_]*$")  // therewith.  type LabelName string -// IsValid is true iff the label name matches the pattern of LabelNameRE. This -// method, however, does not use LabelNameRE for the check but a much faster -// hardcoded implementation. +// IsValid returns true iff name matches the pattern of LabelNameRE for legacy +// names, and iff it's valid UTF-8 if NameValidationScheme is set to +// UTF8Validation. For the legacy matching, it does not use LabelNameRE for the +// check but a much faster hardcoded implementation.  func (ln LabelName) IsValid() bool {  	if len(ln) == 0 {  		return false  	} -	for i, b := range ln { -		if !((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || (b >= '0' && b <= '9' && i > 0)) { -			return false +	switch NameValidationScheme { +	case LegacyValidation: +		for i, b := range ln { +			if !((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || (b >= '0' && b <= '9' && i > 0)) { +				return false +			}  		} +	case UTF8Validation: +		return utf8.ValidString(string(ln)) +	default: +		panic(fmt.Sprintf("Invalid name validation scheme requested: %d", NameValidationScheme))  	}  	return true  } @@ -164,7 +172,7 @@ func (l LabelNames) String() string {  // A LabelValue is an associated value for a LabelName.  type LabelValue string -// IsValid returns true iff the string is a valid UTF8. +// IsValid returns true iff the string is a valid UTF-8.  func (lv LabelValue) IsValid() bool {  	return utf8.ValidString(string(lv))  } diff --git a/vendor/github.com/prometheus/common/model/metadata.go b/vendor/github.com/prometheus/common/model/metadata.go new file mode 100644 index 000000000..447ab8ad6 --- /dev/null +++ b/vendor/github.com/prometheus/common/model/metadata.go @@ -0,0 +1,28 @@ +// Copyright 2023 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. + +package model + +// MetricType represents metric type values. +type MetricType string + +const ( +	MetricTypeCounter        = MetricType("counter") +	MetricTypeGauge          = MetricType("gauge") +	MetricTypeHistogram      = MetricType("histogram") +	MetricTypeGaugeHistogram = MetricType("gaugehistogram") +	MetricTypeSummary        = MetricType("summary") +	MetricTypeInfo           = MetricType("info") +	MetricTypeStateset       = MetricType("stateset") +	MetricTypeUnknown        = MetricType("unknown") +) diff --git a/vendor/github.com/prometheus/common/model/metric.go b/vendor/github.com/prometheus/common/model/metric.go index 00804b7fe..0bd29b3a3 100644 --- a/vendor/github.com/prometheus/common/model/metric.go +++ b/vendor/github.com/prometheus/common/model/metric.go @@ -18,15 +18,84 @@ import (  	"regexp"  	"sort"  	"strings" +	"unicode/utf8" + +	dto "github.com/prometheus/client_model/go" +	"google.golang.org/protobuf/proto"  )  var ( -	// MetricNameRE is a regular expression matching valid metric -	// names. Note that the IsValidMetricName function performs the same -	// check but faster than a match with this regular expression. -	MetricNameRE = regexp.MustCompile(`^[a-zA-Z_:][a-zA-Z0-9_:]*$`) +	// NameValidationScheme determines the method of name validation to be used by +	// all calls to IsValidMetricName() and LabelName IsValid(). Setting UTF-8 mode +	// in isolation from other components that don't support UTF-8 may result in +	// bugs or other undefined behavior. This value is intended to be set by +	// UTF-8-aware binaries as part of their startup. To avoid need for locking, +	// this value should be set once, ideally in an init(), before multiple +	// goroutines are started. +	NameValidationScheme = LegacyValidation + +	// NameEscapingScheme defines the default way that names will be +	// escaped when presented to systems that do not support UTF-8 names. If the +	// Content-Type "escaping" term is specified, that will override this value. +	NameEscapingScheme = ValueEncodingEscaping +) + +// ValidationScheme is a Go enum for determining how metric and label names will +// be validated by this library. +type ValidationScheme int + +const ( +	// LegacyValidation is a setting that requirets that metric and label names +	// conform to the original Prometheus character requirements described by +	// MetricNameRE and LabelNameRE. +	LegacyValidation ValidationScheme = iota + +	// UTF8Validation only requires that metric and label names be valid UTF-8 +	// strings. +	UTF8Validation +) + +type EscapingScheme int + +const ( +	// NoEscaping indicates that a name will not be escaped. Unescaped names that +	// do not conform to the legacy validity check will use a new exposition +	// format syntax that will be officially standardized in future versions. +	NoEscaping EscapingScheme = iota + +	// UnderscoreEscaping replaces all legacy-invalid characters with underscores. +	UnderscoreEscaping + +	// DotsEscaping is similar to UnderscoreEscaping, except that dots are +	// converted to `_dot_` and pre-existing underscores are converted to `__`. +	DotsEscaping + +	// ValueEncodingEscaping prepends the name with `U__` and replaces all invalid +	// characters with the unicode value, surrounded by underscores. Single +	// underscores are replaced with double underscores. +	ValueEncodingEscaping +) + +const ( +	// EscapingKey is the key in an Accept or Content-Type header that defines how +	// metric and label names that do not conform to the legacy character +	// requirements should be escaped when being scraped by a legacy prometheus +	// system. If a system does not explicitly pass an escaping parameter in the +	// Accept header, the default NameEscapingScheme will be used. +	EscapingKey = "escaping" + +	// Possible values for Escaping Key: +	AllowUTF8         = "allow-utf-8" // No escaping required. +	EscapeUnderscores = "underscores" +	EscapeDots        = "dots" +	EscapeValues      = "values"  ) +// MetricNameRE is a regular expression matching valid metric +// names. Note that the IsValidMetricName function performs the same +// check but faster than a match with this regular expression. +var MetricNameRE = regexp.MustCompile(`^[a-zA-Z_:][a-zA-Z0-9_:]*$`) +  // A Metric is similar to a LabelSet, but the key difference is that a Metric is  // a singleton and refers to one and only one stream of samples.  type Metric LabelSet @@ -86,17 +155,302 @@ func (m Metric) FastFingerprint() Fingerprint {  	return LabelSet(m).FastFingerprint()  } -// IsValidMetricName returns true iff name matches the pattern of MetricNameRE. +// IsValidMetricName returns true iff name matches the pattern of MetricNameRE +// for legacy names, and iff it's valid UTF-8 if the UTF8Validation scheme is +// selected. +func IsValidMetricName(n LabelValue) bool { +	switch NameValidationScheme { +	case LegacyValidation: +		return IsValidLegacyMetricName(n) +	case UTF8Validation: +		if len(n) == 0 { +			return false +		} +		return utf8.ValidString(string(n)) +	default: +		panic(fmt.Sprintf("Invalid name validation scheme requested: %d", NameValidationScheme)) +	} +} + +// IsValidLegacyMetricName is similar to IsValidMetricName but always uses the +// legacy validation scheme regardless of the value of NameValidationScheme.  // This function, however, does not use MetricNameRE for the check but a much  // faster hardcoded implementation. -func IsValidMetricName(n LabelValue) bool { +func IsValidLegacyMetricName(n LabelValue) bool {  	if len(n) == 0 {  		return false  	}  	for i, b := range n { -		if !((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || b == ':' || (b >= '0' && b <= '9' && i > 0)) { +		if !isValidLegacyRune(b, i) {  			return false  		}  	}  	return true  } + +// EscapeMetricFamily escapes the given metric names and labels with the given +// escaping scheme. Returns a new object that uses the same pointers to fields +// when possible and creates new escaped versions so as not to mutate the +// input. +func EscapeMetricFamily(v *dto.MetricFamily, scheme EscapingScheme) *dto.MetricFamily { +	if v == nil { +		return nil +	} + +	if scheme == NoEscaping { +		return v +	} + +	out := &dto.MetricFamily{ +		Help: v.Help, +		Type: v.Type, +	} + +	// If the name is nil, copy as-is, don't try to escape. +	if v.Name == nil || IsValidLegacyMetricName(LabelValue(v.GetName())) { +		out.Name = v.Name +	} else { +		out.Name = proto.String(EscapeName(v.GetName(), scheme)) +	} +	for _, m := range v.Metric { +		if !metricNeedsEscaping(m) { +			out.Metric = append(out.Metric, m) +			continue +		} + +		escaped := &dto.Metric{ +			Gauge:       m.Gauge, +			Counter:     m.Counter, +			Summary:     m.Summary, +			Untyped:     m.Untyped, +			Histogram:   m.Histogram, +			TimestampMs: m.TimestampMs, +		} + +		for _, l := range m.Label { +			if l.GetName() == MetricNameLabel { +				if l.Value == nil || IsValidLegacyMetricName(LabelValue(l.GetValue())) { +					escaped.Label = append(escaped.Label, l) +					continue +				} +				escaped.Label = append(escaped.Label, &dto.LabelPair{ +					Name:  proto.String(MetricNameLabel), +					Value: proto.String(EscapeName(l.GetValue(), scheme)), +				}) +				continue +			} +			if l.Name == nil || IsValidLegacyMetricName(LabelValue(l.GetName())) { +				escaped.Label = append(escaped.Label, l) +				continue +			} +			escaped.Label = append(escaped.Label, &dto.LabelPair{ +				Name:  proto.String(EscapeName(l.GetName(), scheme)), +				Value: l.Value, +			}) +		} +		out.Metric = append(out.Metric, escaped) +	} +	return out +} + +func metricNeedsEscaping(m *dto.Metric) bool { +	for _, l := range m.Label { +		if l.GetName() == MetricNameLabel && !IsValidLegacyMetricName(LabelValue(l.GetValue())) { +			return true +		} +		if !IsValidLegacyMetricName(LabelValue(l.GetName())) { +			return true +		} +	} +	return false +} + +const ( +	lowerhex = "0123456789abcdef" +) + +// EscapeName escapes the incoming name according to the provided escaping +// scheme. Depending on the rules of escaping, this may cause no change in the +// string that is returned. (Especially NoEscaping, which by definition is a +// noop). This function does not do any validation of the name. +func EscapeName(name string, scheme EscapingScheme) string { +	if len(name) == 0 { +		return name +	} +	var escaped strings.Builder +	switch scheme { +	case NoEscaping: +		return name +	case UnderscoreEscaping: +		if IsValidLegacyMetricName(LabelValue(name)) { +			return name +		} +		for i, b := range name { +			if isValidLegacyRune(b, i) { +				escaped.WriteRune(b) +			} else { +				escaped.WriteRune('_') +			} +		} +		return escaped.String() +	case DotsEscaping: +		// Do not early return for legacy valid names, we still escape underscores. +		for i, b := range name { +			if b == '_' { +				escaped.WriteString("__") +			} else if b == '.' { +				escaped.WriteString("_dot_") +			} else if isValidLegacyRune(b, i) { +				escaped.WriteRune(b) +			} else { +				escaped.WriteRune('_') +			} +		} +		return escaped.String() +	case ValueEncodingEscaping: +		if IsValidLegacyMetricName(LabelValue(name)) { +			return name +		} +		escaped.WriteString("U__") +		for i, b := range name { +			if isValidLegacyRune(b, i) { +				escaped.WriteRune(b) +			} else if !utf8.ValidRune(b) { +				escaped.WriteString("_FFFD_") +			} else if b < 0x100 { +				escaped.WriteRune('_') +				for s := 4; s >= 0; s -= 4 { +					escaped.WriteByte(lowerhex[b>>uint(s)&0xF]) +				} +				escaped.WriteRune('_') +			} else if b < 0x10000 { +				escaped.WriteRune('_') +				for s := 12; s >= 0; s -= 4 { +					escaped.WriteByte(lowerhex[b>>uint(s)&0xF]) +				} +				escaped.WriteRune('_') +			} +		} +		return escaped.String() +	default: +		panic(fmt.Sprintf("invalid escaping scheme %d", scheme)) +	} +} + +// lower function taken from strconv.atoi +func lower(c byte) byte { +	return c | ('x' - 'X') +} + +// UnescapeName unescapes the incoming name according to the provided escaping +// scheme if possible. Some schemes are partially or totally non-roundtripable. +// If any error is enountered, returns the original input. +func UnescapeName(name string, scheme EscapingScheme) string { +	if len(name) == 0 { +		return name +	} +	switch scheme { +	case NoEscaping: +		return name +	case UnderscoreEscaping: +		// It is not possible to unescape from underscore replacement. +		return name +	case DotsEscaping: +		name = strings.ReplaceAll(name, "_dot_", ".") +		name = strings.ReplaceAll(name, "__", "_") +		return name +	case ValueEncodingEscaping: +		escapedName, found := strings.CutPrefix(name, "U__") +		if !found { +			return name +		} + +		var unescaped strings.Builder +	TOP: +		for i := 0; i < len(escapedName); i++ { +			// All non-underscores are treated normally. +			if escapedName[i] != '_' { +				unescaped.WriteByte(escapedName[i]) +				continue +			} +			i++ +			if i >= len(escapedName) { +				return name +			} +			// A double underscore is a single underscore. +			if escapedName[i] == '_' { +				unescaped.WriteByte('_') +				continue +			} +			// We think we are in a UTF-8 code, process it. +			var utf8Val uint +			for j := 0; i < len(escapedName); j++ { +				// This is too many characters for a utf8 value. +				if j > 4 { +					return name +				} +				// Found a closing underscore, convert to a rune, check validity, and append. +				if escapedName[i] == '_' { +					utf8Rune := rune(utf8Val) +					if !utf8.ValidRune(utf8Rune) { +						return name +					} +					unescaped.WriteRune(utf8Rune) +					continue TOP +				} +				r := lower(escapedName[i]) +				utf8Val *= 16 +				if r >= '0' && r <= '9' { +					utf8Val += uint(r) - '0' +				} else if r >= 'a' && r <= 'f' { +					utf8Val += uint(r) - 'a' + 10 +				} else { +					return name +				} +				i++ +			} +			// Didn't find closing underscore, invalid. +			return name +		} +		return unescaped.String() +	default: +		panic(fmt.Sprintf("invalid escaping scheme %d", scheme)) +	} +} + +func isValidLegacyRune(b rune, i int) bool { +	return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || b == ':' || (b >= '0' && b <= '9' && i > 0) +} + +func (e EscapingScheme) String() string { +	switch e { +	case NoEscaping: +		return AllowUTF8 +	case UnderscoreEscaping: +		return EscapeUnderscores +	case DotsEscaping: +		return EscapeDots +	case ValueEncodingEscaping: +		return EscapeValues +	default: +		panic(fmt.Sprintf("unknown format scheme %d", e)) +	} +} + +func ToEscapingScheme(s string) (EscapingScheme, error) { +	if s == "" { +		return NoEscaping, fmt.Errorf("got empty string instead of escaping scheme") +	} +	switch s { +	case AllowUTF8: +		return NoEscaping, nil +	case EscapeUnderscores: +		return UnderscoreEscaping, nil +	case EscapeDots: +		return DotsEscaping, nil +	case EscapeValues: +		return ValueEncodingEscaping, nil +	default: +		return NoEscaping, fmt.Errorf("unknown format scheme " + s) +	} +} diff --git a/vendor/github.com/prometheus/common/model/signature.go b/vendor/github.com/prometheus/common/model/signature.go index 8762b13c6..dc8a0026c 100644 --- a/vendor/github.com/prometheus/common/model/signature.go +++ b/vendor/github.com/prometheus/common/model/signature.go @@ -22,10 +22,8 @@ import (  // when calculating their combined hash value (aka signature aka fingerprint).  const SeparatorByte byte = 255 -var ( -	// cache the signature of an empty label set. -	emptyLabelSignature = hashNew() -) +// cache the signature of an empty label set. +var emptyLabelSignature = hashNew()  // LabelsToSignature returns a quasi-unique signature (i.e., fingerprint) for a  // given label set. (Collisions are possible but unlikely if the number of label diff --git a/vendor/github.com/prometheus/common/model/silence.go b/vendor/github.com/prometheus/common/model/silence.go index bb99889d2..910b0b71f 100644 --- a/vendor/github.com/prometheus/common/model/silence.go +++ b/vendor/github.com/prometheus/common/model/silence.go @@ -81,7 +81,7 @@ func (s *Silence) Validate() error {  	}  	for _, m := range s.Matchers {  		if err := m.Validate(); err != nil { -			return fmt.Errorf("invalid matcher: %s", err) +			return fmt.Errorf("invalid matcher: %w", err)  		}  	}  	if s.StartsAt.IsZero() { diff --git a/vendor/github.com/prometheus/common/model/value.go b/vendor/github.com/prometheus/common/model/value.go index 9eb440413..8050637d8 100644 --- a/vendor/github.com/prometheus/common/model/value.go +++ b/vendor/github.com/prometheus/common/model/value.go @@ -21,14 +21,12 @@ import (  	"strings"  ) -var ( -	// ZeroSample is the pseudo zero-value of Sample used to signal a -	// non-existing sample. It is a Sample with timestamp Earliest, value 0.0, -	// and metric nil. Note that the natural zero value of Sample has a timestamp -	// of 0, which is possible to appear in a real Sample and thus not suitable -	// to signal a non-existing Sample. -	ZeroSample = Sample{Timestamp: Earliest} -) +// ZeroSample is the pseudo zero-value of Sample used to signal a +// non-existing sample. It is a Sample with timestamp Earliest, value 0.0, +// and metric nil. Note that the natural zero value of Sample has a timestamp +// of 0, which is possible to appear in a real Sample and thus not suitable +// to signal a non-existing Sample. +var ZeroSample = Sample{Timestamp: Earliest}  // Sample is a sample pair associated with a metric. A single sample must either  // define Value or Histogram but not both. Histogram == nil implies the Value @@ -274,7 +272,7 @@ func (s *Scalar) UnmarshalJSON(b []byte) error {  	value, err := strconv.ParseFloat(f, 64)  	if err != nil { -		return fmt.Errorf("error parsing sample value: %s", err) +		return fmt.Errorf("error parsing sample value: %w", err)  	}  	s.Value = SampleValue(value)  	return nil diff --git a/vendor/github.com/prometheus/common/model/value_float.go b/vendor/github.com/prometheus/common/model/value_float.go index 0f615a705..ae35cc2ab 100644 --- a/vendor/github.com/prometheus/common/model/value_float.go +++ b/vendor/github.com/prometheus/common/model/value_float.go @@ -20,14 +20,12 @@ import (  	"strconv"  ) -var ( -	// ZeroSamplePair is the pseudo zero-value of SamplePair used to signal a -	// non-existing sample pair. It is a SamplePair with timestamp Earliest and -	// value 0.0. Note that the natural zero value of SamplePair has a timestamp -	// of 0, which is possible to appear in a real SamplePair and thus not -	// suitable to signal a non-existing SamplePair. -	ZeroSamplePair = SamplePair{Timestamp: Earliest} -) +// ZeroSamplePair is the pseudo zero-value of SamplePair used to signal a +// non-existing sample pair. It is a SamplePair with timestamp Earliest and +// value 0.0. Note that the natural zero value of SamplePair has a timestamp +// of 0, which is possible to appear in a real SamplePair and thus not +// suitable to signal a non-existing SamplePair. +var ZeroSamplePair = SamplePair{Timestamp: Earliest}  // A SampleValue is a representation of a value for a given sample at a given  // time. | 
