[VOL-5486] Fix deprecated versions
Change-Id: I3e03ea246020547ae75fa92ce8cf5cbba7e8f3bb
Signed-off-by: Abhay Kumar <abhay.kumar@radisys.com>
diff --git a/vendor/github.com/prometheus/common/expfmt/decode.go b/vendor/github.com/prometheus/common/expfmt/decode.go
index 7657f84..7b76237 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 @@
"net/http"
dto "github.com/prometheus/client_model/go"
+ "google.golang.org/protobuf/encoding/protodelim"
- "github.com/matttproud/golang_protobuf_extensions/pbutil"
"github.com/prometheus/common/model"
)
@@ -69,28 +70,45 @@
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.
+// NewDecoder returns a new decoder based on the given input format. Metric
+// names are validated based on the provided Format -- if the format requires
+// escaping, raditional Prometheues validity checking is used. Otherwise, names
+// are checked for UTF-8 validity. Supported formats include delimited protobuf
+// and Prometheus text format. For historical reasons, this decoder fallbacks
+// to classic text decoding for any other format. This decoder does not fully
+// support OpenMetrics although it may often succeed due to the similarities
+// between the formats. This decoder may not support the latest features of
+// Prometheus text format and is not intended for high-performance applications.
+// See: https://github.com/prometheus/common/issues/812
func NewDecoder(r io.Reader, format Format) Decoder {
- switch format {
- case FmtProtoDelim:
- return &protoDecoder{r: r}
+ scheme := model.LegacyValidation
+ if format.ToEscapingScheme() == model.NoEscaping {
+ scheme = model.UTF8Validation
}
- return &textDecoder{r: r}
+ switch format.FormatType() {
+ case TypeProtoDelim:
+ return &protoDecoder{r: bufio.NewReader(r), s: scheme}
+ case TypeProtoText, TypeProtoCompact:
+ return &errDecoder{err: fmt.Errorf("format %s not supported for decoding", format)}
+ }
+ return &textDecoder{r: r, s: scheme}
}
// protoDecoder implements the Decoder interface for protocol buffers.
type protoDecoder struct {
- r io.Reader
+ r protodelim.Reader
+ s model.ValidationScheme
}
// 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(d.r, v); err != nil {
return err
}
- if !model.IsValidMetricName(model.LabelValue(v.GetName())) {
+ if !d.s.IsValidMetricName(v.GetName()) {
return fmt.Errorf("invalid metric name %q", v.GetName())
}
for _, m := range v.GetMetric() {
@@ -104,7 +122,7 @@
if !model.LabelValue(l.GetValue()).IsValid() {
return fmt.Errorf("invalid label value %q", l.GetValue())
}
- if !model.LabelName(l.GetName()).IsValid() {
+ if !d.s.IsValidLabelName(l.GetName()) {
return fmt.Errorf("invalid label name %q", l.GetName())
}
}
@@ -112,35 +130,44 @@
return nil
}
+// errDecoder is an error-state decoder that always returns the same error.
+type errDecoder struct {
+ err error
+}
+
+func (d *errDecoder) Decode(*dto.MetricFamily) error {
+ return d.err
+}
+
// textDecoder implements the Decoder interface for the text protocol.
type textDecoder struct {
r io.Reader
- p TextParser
- fams []*dto.MetricFamily
+ fams map[string]*dto.MetricFamily
+ s model.ValidationScheme
+ err error
}
// Decode implements the Decoder interface.
func (d *textDecoder) Decode(v *dto.MetricFamily) error {
- // TODO(fabxc): Wrap this as a line reader to make streaming safer.
- if len(d.fams) == 0 {
- // No cached metric families, read everything and parse metrics.
- fams, err := d.p.TextToMetricFamilies(d.r)
- if err != nil {
- return err
- }
- if len(fams) == 0 {
- return io.EOF
- }
- d.fams = make([]*dto.MetricFamily, 0, len(fams))
- for _, f := range fams {
- d.fams = append(d.fams, f)
+ if d.err == nil {
+ // Read all metrics in one shot.
+ p := NewTextParser(d.s)
+ d.fams, d.err = p.TextToMetricFamilies(d.r)
+ // If we don't get an error, store io.EOF for the end.
+ if d.err == nil {
+ d.err = io.EOF
}
}
-
- *v = *d.fams[0]
- d.fams = d.fams[1:]
-
- return nil
+ // Pick off one MetricFamily per Decode until there's nothing left.
+ for key, fam := range d.fams {
+ v.Name = fam.Name
+ v.Help = fam.Help
+ v.Type = fam.Type
+ v.Metric = fam.Metric
+ delete(d.fams, key)
+ return nil
+ }
+ return d.err
}
// SampleDecoder wraps a Decoder to extract samples from the metric families
diff --git a/vendor/github.com/prometheus/common/expfmt/encode.go b/vendor/github.com/prometheus/common/expfmt/encode.go
index bd4e347..73c24df 100644
--- a/vendor/github.com/prometheus/common/expfmt/encode.go
+++ b/vendor/github.com/prometheus/common/expfmt/encode.go
@@ -18,11 +18,12 @@
"io"
"net/http"
- "github.com/golang/protobuf/proto"
- "github.com/matttproud/golang_protobuf_extensions/pbutil"
- "github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg"
-
+ "github.com/munnerz/goautoneg"
dto "github.com/prometheus/client_model/go"
+ "google.golang.org/protobuf/encoding/protodelim"
+ "google.golang.org/protobuf/encoding/prototext"
+
+ "github.com/prometheus/common/model"
)
// Encoder types encode metric families into an underlying wire protocol.
@@ -58,25 +59,34 @@
// appropriate accepted type is found, FmtText is returned (which is the
// Prometheus text format). This function will never negotiate FmtOpenMetrics,
// as the support is still experimental. To include the option to negotiate
-// FmtOpenMetrics, use NegotiateOpenMetrics.
+// FmtOpenMetrics, use NegotiateIncludingOpenMetrics.
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("; escaping=" + 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,26 +94,40 @@
// 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("; escaping=" + 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 || ver == "") {
- return FmtOpenMetrics
+ if ac.Type+"/"+ac.SubType == OpenMetricsType && (ver == OpenMetricsVersion_0_0_1 || ver == OpenMetricsVersion_1_0_0 || ver == "") {
+ switch ver {
+ case OpenMetricsVersion_1_0_0:
+ return FmtOpenMetrics_1_0_0 + escapingScheme
+ default:
+ return FmtOpenMetrics_0_0_1 + escapingScheme
+ }
}
}
- return FmtText
+ return FmtText + escapingScheme
}
// NewEncoder returns a new encoder based on content type negotiation. All
@@ -112,44 +136,54 @@
// 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.
-func NewEncoder(w io.Writer, format Format) Encoder {
- switch format {
- case FmtProtoDelim:
+// In cases where the Format does not allow for UTF-8 names, the global
+// NameEscapingScheme will be applied.
+//
+// NewEncoder can be called with additional options to customize the OpenMetrics text output.
+// For example:
+// NewEncoder(w, FmtOpenMetrics_1_0_0, WithCreatedLines())
+//
+// Extra options are ignored for all other formats.
+func NewEncoder(w io.Writer, format Format, options ...EncoderOption) Encoder {
+ 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, model.EscapeMetricFamily(v, escapingScheme))
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, proto.MarshalTextString(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:
+ case TypeOpenMetrics:
return encoderCloser{
encode: func(v *dto.MetricFamily) error {
- _, err := MetricFamilyToOpenMetrics(w, v)
+ _, err := MetricFamilyToOpenMetrics(w, model.EscapeMetricFamily(v, escapingScheme), options...)
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 0f176fa..c34c7de 100644
--- a/vendor/github.com/prometheus/common/expfmt/expfmt.go
+++ b/vendor/github.com/prometheus/common/expfmt/expfmt.go
@@ -14,28 +14,198 @@
// Package expfmt contains tools for reading and writing Prometheus metrics.
package expfmt
+import (
+ "errors"
+ "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 + ";"
- OpenMetricsType = `application/openmetrics-text`
- OpenMetricsVersion = "0.0.1"
+ 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 + ";"
+ OpenMetricsType = `application/openmetrics-text`
+ //nolint:revive // Allow for underscores.
+ OpenMetricsVersion_0_0_1 = "0.0.1"
+ //nolint:revive // Allow for underscores.
+ 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`
+ // 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 = `<unknown>`
+ // 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.
FmtProtoCompact Format = ProtoFmt + ` encoding=compact-text`
- FmtOpenMetrics Format = OpenMetricsType + `; version=` + OpenMetricsVersion + `; charset=utf-8`
+ // 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`
+ // Deprecated: Use expfmt.NewFormat(expfmt.TypeOpenMetrics) instead.
+ //nolint:revive // Allow for underscores.
+ 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 FormatType = 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. If a type has more than one version, the latest version will be
+// returned.
+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
+ }
+}
+
+// NewOpenMetricsFormat generates a new OpenMetrics format matching the
+// specified version number.
+func NewOpenMetricsFormat(version string) (Format, error) {
+ if version == OpenMetricsVersion_0_0_1 {
+ return FmtOpenMetrics_0_0_1, nil
+ }
+ if version == OpenMetricsVersion_1_0_0 {
+ return FmtOpenMetrics_1_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.Split(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, "; "))
+}
+
+// FormatType deduces an overall FormatType for the given format.
+func (f Format) FormatType() FormatType {
+ toks := strings.Split(string(f), ";")
+ 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 (f Format) ToEscapingScheme() model.EscapingScheme {
+ for _, p := range strings.Split(string(f), ";") {
+ 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/fuzz.go b/vendor/github.com/prometheus/common/expfmt/fuzz.go
index dc2eede..0290f6a 100644
--- a/vendor/github.com/prometheus/common/expfmt/fuzz.go
+++ b/vendor/github.com/prometheus/common/expfmt/fuzz.go
@@ -12,22 +12,26 @@
// limitations under the License.
// Build only when actually fuzzing
+//go:build gofuzz
// +build gofuzz
package expfmt
-import "bytes"
+import (
+ "bytes"
+
+ "github.com/prometheus/common/model"
+)
// Fuzz text metric parser with with github.com/dvyukov/go-fuzz:
//
-// go-fuzz-build github.com/prometheus/common/expfmt
-// go-fuzz -bin expfmt-fuzz.zip -workdir fuzz
+// go-fuzz-build github.com/prometheus/common/expfmt
+// go-fuzz -bin expfmt-fuzz.zip -workdir fuzz
//
// Further input samples should go in the folder fuzz/corpus.
func Fuzz(in []byte) int {
- parser := TextParser{}
+ parser := NewTextParser(model.UTF8Validation)
_, err := parser.TextToMetricFamilies(bytes.NewReader(in))
-
if err != nil {
return 0
}
diff --git a/vendor/github.com/prometheus/common/expfmt/openmetrics_create.go b/vendor/github.com/prometheus/common/expfmt/openmetrics_create.go
index 8a9313a..8dbf6d0 100644
--- a/vendor/github.com/prometheus/common/expfmt/openmetrics_create.go
+++ b/vendor/github.com/prometheus/common/expfmt/openmetrics_create.go
@@ -22,12 +22,46 @@
"strconv"
"strings"
- "github.com/golang/protobuf/ptypes"
- "github.com/prometheus/common/model"
-
dto "github.com/prometheus/client_model/go"
+ "google.golang.org/protobuf/types/known/timestamppb"
+
+ "github.com/prometheus/common/model"
)
+type encoderOption struct {
+ withCreatedLines bool
+ withUnit bool
+}
+
+type EncoderOption func(*encoderOption)
+
+// WithCreatedLines is an EncoderOption that configures the OpenMetrics encoder
+// to include _created lines (See
+// https://github.com/prometheus/OpenMetrics/blob/v1.0.0/specification/OpenMetrics.md#counter-1).
+// Created timestamps can improve the accuracy of series reset detection, but
+// come with a bandwidth cost.
+//
+// At the time of writing, created timestamp ingestion is still experimental in
+// Prometheus and need to be enabled with the feature-flag
+// `--feature-flag=created-timestamp-zero-ingestion`, and breaking changes are
+// still possible. Therefore, it is recommended to use this feature with caution.
+func WithCreatedLines() EncoderOption {
+ return func(t *encoderOption) {
+ t.withCreatedLines = true
+ }
+}
+
+// WithUnit is an EncoderOption enabling a set unit to be written to the output
+// and to be added to the metric name, if it's not there already, as a suffix.
+// Without opting in this way, the unit will not be added to the metric name and,
+// on top of that, the unit will not be passed onto the output, even if it
+// were declared in the *dto.MetricFamily struct, i.e. even if in.Unit !=nil.
+func WithUnit() EncoderOption {
+ return func(t *encoderOption) {
+ t.withUnit = true
+ }
+}
+
// MetricFamilyToOpenMetrics converts a MetricFamily proto message into the
// OpenMetrics text format and writes the resulting lines to 'out'. It returns
// the number of bytes written and any error encountered. The output will have
@@ -36,6 +70,18 @@
// 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
@@ -47,21 +93,35 @@
// missing features and peculiarities to avoid complications when switching from
// Prometheus to OpenMetrics or vice versa:
//
-// - Counters are expected to have the `_total` suffix in their metric name. In
-// the output, the suffix will be truncated from the `# TYPE` and `# HELP`
-// line. A counter with a missing `_total` suffix is not an error. However,
-// its type will be set to `unknown` in that case to avoid invalid OpenMetrics
-// output.
+// - Counters are expected to have the `_total` suffix in their metric name. In
+// the output, the suffix will be truncated from the `# TYPE`, `# HELP` and `# UNIT`
+// lines. A counter with a missing `_total` suffix is not an error. However,
+// its type will be set to `unknown` in that case to avoid invalid OpenMetrics
+// output.
//
-// - No support for the following (optional) features: `# UNIT` line, `_created`
-// line, info type, stateset type, gaugehistogram type.
+// - According to the OM specs, the `# UNIT` line is optional, but if populated,
+// the unit has to be present in the metric name as its suffix:
+// (see https://github.com/prometheus/OpenMetrics/blob/v1.0.0/specification/OpenMetrics.md#unit).
+// However, in order to accommodate any potential scenario where such a change in the
+// metric name is not desirable, the users are here given the choice of either explicitly
+// opt in, in case they wish for the unit to be included in the output AND in the metric name
+// as a suffix (see the description of the WithUnit function above),
+// or not to opt in, in case they don't want for any of that to happen.
//
-// - The size of exemplar labels is not checked (i.e. it's possible to create
-// exemplars that are larger than allowed by the OpenMetrics specification).
+// - No support for the following (optional) features: info type,
+// stateset type, gaugehistogram type.
//
-// - The value of Counters is not checked. (OpenMetrics doesn't allow counters
-// with a `NaN` value.)
-func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int, err error) {
+// - The size of exemplar labels is not checked (i.e. it's possible to create
+// exemplars that are larger than allowed by the OpenMetrics specification).
+//
+// - The value of Counters is not checked. (OpenMetrics doesn't allow counters
+// with a `NaN` value.)
+func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily, options ...EncoderOption) (written int, err error) {
+ toOM := encoderOption{}
+ for _, option := range options {
+ option(&toOM)
+ }
+
name := in.GetName()
if name == "" {
return 0, fmt.Errorf("MetricFamily has no name: %s", in)
@@ -84,12 +144,15 @@
}
var (
- n int
- metricType = in.GetType()
- shortName = name
+ n int
+ metricType = in.GetType()
+ compliantName = name
)
- if metricType == dto.MetricType_COUNTER && strings.HasSuffix(shortName, "_total") {
- shortName = name[:len(name)-6]
+ if metricType == dto.MetricType_COUNTER && strings.HasSuffix(compliantName, "_total") {
+ compliantName = name[:len(name)-6]
+ }
+ if toOM.withUnit && in.Unit != nil && !strings.HasSuffix(compliantName, "_"+*in.Unit) {
+ compliantName = compliantName + "_" + *in.Unit
}
// Comments, first HELP, then TYPE.
@@ -99,7 +162,7 @@
if err != nil {
return
}
- n, err = w.WriteString(shortName)
+ n, err = writeName(w, compliantName)
written += n
if err != nil {
return
@@ -125,7 +188,7 @@
if err != nil {
return
}
- n, err = w.WriteString(shortName)
+ n, err = writeName(w, compliantName)
written += n
if err != nil {
return
@@ -152,55 +215,89 @@
if err != nil {
return
}
+ if toOM.withUnit && in.Unit != nil {
+ n, err = w.WriteString("# UNIT ")
+ written += n
+ if err != nil {
+ return
+ }
+ n, err = writeName(w, compliantName)
+ written += n
+ if err != nil {
+ return
+ }
+
+ err = w.WriteByte(' ')
+ written++
+ if err != nil {
+ return
+ }
+ n, err = writeEscapedString(w, *in.Unit, true)
+ written += n
+ if err != nil {
+ return
+ }
+ err = w.WriteByte('\n')
+ written++
+ if err != nil {
+ return
+ }
+ }
+
+ var createdTsBytesWritten int
// Finally the samples, one line for each.
+ if metricType == dto.MetricType_COUNTER && strings.HasSuffix(name, "_total") {
+ compliantName += "_total"
+ }
for _, metric := range in.Metric {
switch metricType {
case dto.MetricType_COUNTER:
if metric.Counter == nil {
return written, fmt.Errorf(
- "expected counter in metric %s %s", name, metric,
+ "expected counter in metric %s %s", compliantName, metric,
)
}
- // Note that we have ensured above that either the name
- // ends on `_total` or that the rendered type is
- // `unknown`. Therefore, no `_total` must be added here.
n, err = writeOpenMetricsSample(
- w, name, "", metric, "", 0,
+ w, compliantName, "", metric, "", 0,
metric.Counter.GetValue(), 0, false,
metric.Counter.Exemplar,
)
+ if toOM.withCreatedLines && metric.Counter.CreatedTimestamp != nil {
+ createdTsBytesWritten, err = writeOpenMetricsCreated(w, compliantName, "_total", metric, "", 0, metric.Counter.GetCreatedTimestamp())
+ n += createdTsBytesWritten
+ }
case dto.MetricType_GAUGE:
if metric.Gauge == nil {
return written, fmt.Errorf(
- "expected gauge in metric %s %s", name, metric,
+ "expected gauge in metric %s %s", compliantName, metric,
)
}
n, err = writeOpenMetricsSample(
- w, name, "", metric, "", 0,
+ w, compliantName, "", metric, "", 0,
metric.Gauge.GetValue(), 0, false,
nil,
)
case dto.MetricType_UNTYPED:
if metric.Untyped == nil {
return written, fmt.Errorf(
- "expected untyped in metric %s %s", name, metric,
+ "expected untyped in metric %s %s", compliantName, metric,
)
}
n, err = writeOpenMetricsSample(
- w, name, "", metric, "", 0,
+ w, compliantName, "", metric, "", 0,
metric.Untyped.GetValue(), 0, false,
nil,
)
case dto.MetricType_SUMMARY:
if metric.Summary == nil {
return written, fmt.Errorf(
- "expected summary in metric %s %s", name, metric,
+ "expected summary in metric %s %s", compliantName, metric,
)
}
for _, q := range metric.Summary.Quantile {
n, err = writeOpenMetricsSample(
- w, name, "", metric,
+ w, compliantName, "", metric,
model.QuantileLabel, q.GetQuantile(),
q.GetValue(), 0, false,
nil,
@@ -211,7 +308,7 @@
}
}
n, err = writeOpenMetricsSample(
- w, name, "_sum", metric, "", 0,
+ w, compliantName, "_sum", metric, "", 0,
metric.Summary.GetSampleSum(), 0, false,
nil,
)
@@ -220,20 +317,24 @@
return
}
n, err = writeOpenMetricsSample(
- w, name, "_count", metric, "", 0,
+ w, compliantName, "_count", metric, "", 0,
0, metric.Summary.GetSampleCount(), true,
nil,
)
+ if toOM.withCreatedLines && metric.Summary.CreatedTimestamp != nil {
+ createdTsBytesWritten, err = writeOpenMetricsCreated(w, compliantName, "", metric, "", 0, metric.Summary.GetCreatedTimestamp())
+ n += createdTsBytesWritten
+ }
case dto.MetricType_HISTOGRAM:
if metric.Histogram == nil {
return written, fmt.Errorf(
- "expected histogram in metric %s %s", name, metric,
+ "expected histogram in metric %s %s", compliantName, metric,
)
}
infSeen := false
for _, b := range metric.Histogram.Bucket {
n, err = writeOpenMetricsSample(
- w, name, "_bucket", metric,
+ w, compliantName, "_bucket", metric,
model.BucketLabel, b.GetUpperBound(),
0, b.GetCumulativeCount(), true,
b.Exemplar,
@@ -248,7 +349,7 @@
}
if !infSeen {
n, err = writeOpenMetricsSample(
- w, name, "_bucket", metric,
+ w, compliantName, "_bucket", metric,
model.BucketLabel, math.Inf(+1),
0, metric.Histogram.GetSampleCount(), true,
nil,
@@ -259,7 +360,7 @@
}
}
n, err = writeOpenMetricsSample(
- w, name, "_sum", metric, "", 0,
+ w, compliantName, "_sum", metric, "", 0,
metric.Histogram.GetSampleSum(), 0, false,
nil,
)
@@ -268,13 +369,17 @@
return
}
n, err = writeOpenMetricsSample(
- w, name, "_count", metric, "", 0,
+ w, compliantName, "_count", metric, "", 0,
0, metric.Histogram.GetSampleCount(), true,
nil,
)
+ if toOM.withCreatedLines && metric.Histogram.CreatedTimestamp != nil {
+ createdTsBytesWritten, err = writeOpenMetricsCreated(w, compliantName, "", metric, "", 0, metric.Histogram.GetCreatedTimestamp())
+ n += createdTsBytesWritten
+ }
default:
return written, fmt.Errorf(
- "unexpected type in metric %s %s", name, metric,
+ "unexpected type in metric %s %s", compliantName, metric,
)
}
written += n
@@ -304,21 +409,9 @@
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 {
@@ -351,7 +444,7 @@
return written, err
}
}
- if exemplar != nil {
+ if exemplar != nil && len(exemplar.Label) > 0 {
n, err = writeExemplar(w, exemplar)
written += n
if err != nil {
@@ -366,27 +459,58 @@
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.LegacyValidation.IsValidMetricName(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
@@ -443,6 +567,49 @@
return written, nil
}
+// writeOpenMetricsCreated writes the created timestamp for a single time series
+// following OpenMetrics text format to w, given the metric name, the metric proto
+// message itself, optionally a suffix to be removed, e.g. '_total' for counters,
+// an additional label name with a float64 value (use empty string as label name if
+// not required) and the timestamp that represents the created timestamp.
+// The function returns the number of bytes written and any error encountered.
+func writeOpenMetricsCreated(w enhancedWriter,
+ name, suffixToTrim string, metric *dto.Metric,
+ additionalLabelName string, additionalLabelValue float64,
+ createdTimestamp *timestamppb.Timestamp,
+) (int, error) {
+ written := 0
+ n, err := writeOpenMetricsNameAndLabelPairs(
+ w, strings.TrimSuffix(name, suffixToTrim)+"_created", metric.Label, additionalLabelName, additionalLabelValue,
+ )
+ written += n
+ if err != nil {
+ return written, err
+ }
+
+ err = w.WriteByte(' ')
+ written++
+ if err != nil {
+ return written, err
+ }
+
+ // TODO(beorn7): Format this directly from components of ts to
+ // avoid overflow/underflow and precision issues of the float
+ // conversion.
+ n, err = writeOpenMetricsFloat(w, float64(createdTimestamp.AsTime().UnixNano())/1e9)
+ written += n
+ if err != nil {
+ return written, err
+ }
+
+ err = w.WriteByte('\n')
+ written++
+ if err != nil {
+ return written, err
+ }
+ return written, nil
+}
+
// writeExemplar writes the provided exemplar in OpenMetrics format to w. The
// function returns the number of bytes written and any error encountered.
func writeExemplar(w enhancedWriter, e *dto.Exemplar) (int, error) {
@@ -452,7 +619,7 @@
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
@@ -473,10 +640,11 @@
if err != nil {
return written, err
}
- ts, err := ptypes.Timestamp((*e).Timestamp)
+ err = e.Timestamp.CheckValid()
if err != nil {
return written, err
}
+ ts := e.Timestamp.AsTime()
// TODO(beorn7): Format this directly from components of ts to
// avoid overflow/underflow and precision issues of the float
// conversion.
diff --git a/vendor/github.com/prometheus/common/expfmt/text_create.go b/vendor/github.com/prometheus/common/expfmt/text_create.go
index 5ba503b..c4e9c1b 100644
--- a/vendor/github.com/prometheus/common/expfmt/text_create.go
+++ b/vendor/github.com/prometheus/common/expfmt/text_create.go
@@ -17,15 +17,14 @@
"bufio"
"fmt"
"io"
- "io/ioutil"
"math"
"strconv"
"strings"
"sync"
- "github.com/prometheus/common/model"
-
dto "github.com/prometheus/client_model/go"
+
+ "github.com/prometheus/common/model"
)
// enhancedWriter has all the enhanced write functions needed here. bufio.Writer
@@ -44,7 +43,7 @@
var (
bufPool = sync.Pool{
New: func() interface{} {
- return bufio.NewWriter(ioutil.Discard)
+ return bufio.NewWriter(io.Discard)
},
}
numBufPool = sync.Pool{
@@ -63,6 +62,18 @@
// 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.
@@ -99,7 +110,7 @@
if err != nil {
return
}
- n, err = w.WriteString(name)
+ n, err = writeName(w, name)
written += n
if err != nil {
return
@@ -125,7 +136,7 @@
if err != nil {
return
}
- n, err = w.WriteString(name)
+ n, err = writeName(w, name)
written += n
if err != nil {
return
@@ -281,21 +292,9 @@
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 {
@@ -331,32 +330,64 @@
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.LegacyValidation.IsValidMetricName(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
@@ -463,3 +494,27 @@
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.LegacyValidation.IsValidMetricName(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 b6079b3..8f2edde 100644
--- a/vendor/github.com/prometheus/common/expfmt/text_parse.go
+++ b/vendor/github.com/prometheus/common/expfmt/text_parse.go
@@ -16,15 +16,17 @@
import (
"bufio"
"bytes"
+ "errors"
"fmt"
"io"
"math"
"strconv"
"strings"
+ "unicode/utf8"
dto "github.com/prometheus/client_model/go"
+ "google.golang.org/protobuf/proto"
- "github.com/golang/protobuf/proto"
"github.com/prometheus/common/model"
)
@@ -58,6 +60,7 @@
currentMF *dto.MetricFamily
currentMetric *dto.Metric
currentLabelPair *dto.LabelPair
+ currentLabelPairs []*dto.LabelPair // Temporarily stores label pairs while parsing a metric line.
// The remaining member variables are only used for summaries/histograms.
currentLabels map[string]string // All labels including '__name__' but excluding 'quantile'/'le'
@@ -72,6 +75,17 @@
// count and sum of that summary/histogram.
currentIsSummaryCount, currentIsSummarySum bool
currentIsHistogramCount, currentIsHistogramSum bool
+ // These indicate if the metric name from the current line being parsed is inside
+ // braces and if that metric name was found respectively.
+ currentMetricIsInsideBraces, currentMetricInsideBracesIsPresent bool
+ // scheme sets the desired ValidationScheme for names. Defaults to the invalid
+ // UnsetValidation.
+ scheme model.ValidationScheme
+}
+
+// NewTextParser returns a new TextParser with the provided nameValidationScheme.
+func NewTextParser(nameValidationScheme model.ValidationScheme) TextParser {
+ return TextParser{scheme: nameValidationScheme}
}
// TextToMetricFamilies reads 'in' as the simple and flat text-based exchange
@@ -112,7 +126,7 @@
// 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
@@ -120,6 +134,7 @@
func (p *TextParser) reset(in io.Reader) {
p.metricFamiliesByName = map[string]*dto.MetricFamily{}
+ p.currentLabelPairs = nil
if p.buf == nil {
p.buf = bufio.NewReader(in)
} else {
@@ -135,16 +150,23 @@
}
p.currentQuantile = math.NaN()
p.currentBucket = math.NaN()
+ p.currentMF = nil
}
// startOfLine represents the state where the next byte read from p.buf is the
// start of a line (or whitespace leading up to it).
func (p *TextParser) startOfLine() stateFn {
p.lineCount++
+ p.currentMetricIsInsideBraces = false
+ p.currentMetricInsideBracesIsPresent = false
if p.skipBlankTab(); p.err != nil {
- // End of input reached. This is the only case where
- // that is not an error but a signal that we are done.
- p.err = nil
+ // This is the only place that we expect to see io.EOF,
+ // 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 errors.Is(p.err, io.EOF) {
+ p.err = nil
+ }
return nil
}
switch p.currentByte {
@@ -152,6 +174,9 @@
return p.startComment
case '\n':
return p.startOfLine // Empty line, start the next one.
+ case '{':
+ p.currentMetricIsInsideBraces = true
+ return p.readingLabels
}
return p.readingMetricName
}
@@ -200,6 +225,9 @@
return nil
}
p.setOrCreateCurrentMF()
+ if p.err != nil {
+ return nil
+ }
if p.skipBlankTab(); p.err != nil {
return nil // Unexpected end of input.
}
@@ -228,6 +256,9 @@
return nil
}
p.setOrCreateCurrentMF()
+ if p.err != nil {
+ return nil
+ }
// Now is the time to fix the type if it hasn't happened yet.
if p.currentMF.Type == nil {
p.currentMF.Type = dto.MetricType_UNTYPED.Enum()
@@ -269,6 +300,8 @@
return nil // Unexpected end of input.
}
if p.currentByte == '}' {
+ p.currentMetric.Label = append(p.currentMetric.Label, p.currentLabelPairs...)
+ p.currentLabelPairs = nil
if p.skipBlankTab(); p.err != nil {
return nil // Unexpected end of input.
}
@@ -281,34 +314,79 @@
p.parseError(fmt.Sprintf("invalid label name for metric %q", p.currentMF.GetName()))
return nil
}
- p.currentLabelPair = &dto.LabelPair{Name: proto.String(p.currentToken.String())}
- if p.currentLabelPair.GetName() == string(model.MetricNameLabel) {
- p.parseError(fmt.Sprintf("label name %q is reserved", model.MetricNameLabel))
- return nil
- }
- // Special summary/histogram treatment. Don't add 'quantile' and 'le'
- // labels to 'real' labels.
- if !(p.currentMF.GetType() == dto.MetricType_SUMMARY && p.currentLabelPair.GetName() == model.QuantileLabel) &&
- !(p.currentMF.GetType() == dto.MetricType_HISTOGRAM && p.currentLabelPair.GetName() == model.BucketLabel) {
- p.currentMetric.Label = append(p.currentMetric.Label, p.currentLabelPair)
- }
if p.skipBlankTabIfCurrentBlankTab(); p.err != nil {
return nil // Unexpected end of input.
}
if p.currentByte != '=' {
+ if p.currentMetricIsInsideBraces {
+ if p.currentMetricInsideBracesIsPresent {
+ p.parseError(fmt.Sprintf("multiple metric names for metric %q", p.currentMF.GetName()))
+ return nil
+ }
+ switch p.currentByte {
+ case ',':
+ p.setOrCreateCurrentMF()
+ if p.err != nil {
+ return nil
+ }
+ if p.currentMF.Type == nil {
+ p.currentMF.Type = dto.MetricType_UNTYPED.Enum()
+ }
+ p.currentMetric = &dto.Metric{}
+ p.currentMetricInsideBracesIsPresent = true
+ return p.startLabelName
+ case '}':
+ p.setOrCreateCurrentMF()
+ if p.err != nil {
+ p.currentLabelPairs = nil
+ return nil
+ }
+ if p.currentMF.Type == nil {
+ p.currentMF.Type = dto.MetricType_UNTYPED.Enum()
+ }
+ p.currentMetric = &dto.Metric{}
+ p.currentMetric.Label = append(p.currentMetric.Label, p.currentLabelPairs...)
+ p.currentLabelPairs = nil
+ if p.skipBlankTab(); p.err != nil {
+ return nil // Unexpected end of input.
+ }
+ return p.readingValue
+ default:
+ p.parseError(fmt.Sprintf("unexpected end of metric name %q", p.currentByte))
+ return nil
+ }
+ }
p.parseError(fmt.Sprintf("expected '=' after label name, found %q", p.currentByte))
+ p.currentLabelPairs = nil
return nil
}
+ p.currentLabelPair = &dto.LabelPair{Name: proto.String(p.currentToken.String())}
+ if p.currentLabelPair.GetName() == string(model.MetricNameLabel) {
+ p.parseError(fmt.Sprintf("label name %q is reserved", model.MetricNameLabel))
+ p.currentLabelPairs = nil
+ return nil
+ }
+ if !p.scheme.IsValidLabelName(p.currentLabelPair.GetName()) {
+ p.parseError(fmt.Sprintf("invalid label name %q", p.currentLabelPair.GetName()))
+ p.currentLabelPairs = nil
+ return nil
+ }
+ // Special summary/histogram treatment. Don't add 'quantile' and 'le'
+ // labels to 'real' labels.
+ if (p.currentMF.GetType() != dto.MetricType_SUMMARY || p.currentLabelPair.GetName() != model.QuantileLabel) &&
+ (p.currentMF.GetType() != dto.MetricType_HISTOGRAM || p.currentLabelPair.GetName() != model.BucketLabel) {
+ p.currentLabelPairs = append(p.currentLabelPairs, p.currentLabelPair)
+ }
// Check for duplicate label names.
labels := make(map[string]struct{})
- for _, l := range p.currentMetric.Label {
+ for _, l := range p.currentLabelPairs {
lName := l.GetName()
- if _, exists := labels[lName]; !exists {
- labels[lName] = struct{}{}
- } else {
+ if _, exists := labels[lName]; exists {
p.parseError(fmt.Sprintf("duplicate label names for metric %q", p.currentMF.GetName()))
+ p.currentLabelPairs = nil
return nil
}
+ labels[lName] = struct{}{}
}
return p.startLabelValue
}
@@ -339,6 +417,7 @@
if p.currentQuantile, p.err = parseFloat(p.currentLabelPair.GetValue()); p.err != nil {
// Create a more helpful error message.
p.parseError(fmt.Sprintf("expected float as value for 'quantile' label, got %q", p.currentLabelPair.GetValue()))
+ p.currentLabelPairs = nil
return nil
}
} else {
@@ -365,12 +444,19 @@
return p.startLabelName
case '}':
+ if p.currentMF == nil {
+ p.parseError("invalid metric name")
+ return nil
+ }
+ p.currentMetric.Label = append(p.currentMetric.Label, p.currentLabelPairs...)
+ p.currentLabelPairs = nil
if p.skipBlankTab(); p.err != nil {
return nil // Unexpected end of input.
}
return p.readingValue
default:
p.parseError(fmt.Sprintf("unexpected end of label value %q", p.currentLabelPair.GetValue()))
+ p.currentLabelPairs = nil
return nil
}
}
@@ -381,7 +467,8 @@
// When we are here, we have read all the labels, so for the
// special case of a summary/histogram, we can finally find out
// if the metric already exists.
- if p.currentMF.GetType() == dto.MetricType_SUMMARY {
+ switch p.currentMF.GetType() {
+ case dto.MetricType_SUMMARY:
signature := model.LabelsToSignature(p.currentLabels)
if summary := p.summaries[signature]; summary != nil {
p.currentMetric = summary
@@ -389,7 +476,7 @@
p.summaries[signature] = p.currentMetric
p.currentMF.Metric = append(p.currentMF.Metric, p.currentMetric)
}
- } else if p.currentMF.GetType() == dto.MetricType_HISTOGRAM {
+ case dto.MetricType_HISTOGRAM:
signature := model.LabelsToSignature(p.currentLabels)
if histogram := p.histograms[signature]; histogram != nil {
p.currentMetric = histogram
@@ -397,7 +484,7 @@
p.histograms[signature] = p.currentMetric
p.currentMF.Metric = append(p.currentMF.Metric, p.currentMetric)
}
- } else {
+ default:
p.currentMF.Metric = append(p.currentMF.Metric, p.currentMetric)
}
if p.readTokenUntilWhitespace(); p.err != nil {
@@ -579,6 +666,8 @@
p.currentToken.WriteByte(p.currentByte)
case 'n':
p.currentToken.WriteByte('\n')
+ case '"':
+ p.currentToken.WriteByte('"')
default:
p.parseError(fmt.Sprintf("invalid escape sequence '\\%c'", p.currentByte))
return
@@ -604,13 +693,45 @@
// but not into p.currentToken.
func (p *TextParser) readTokenAsMetricName() {
p.currentToken.Reset()
+ // A UTF-8 metric name must be quoted and may have escaped characters.
+ quoted := false
+ escaped := false
if !isValidMetricNameStart(p.currentByte) {
return
}
- for {
- p.currentToken.WriteByte(p.currentByte)
+ for p.err == nil {
+ if escaped {
+ switch p.currentByte {
+ case '\\':
+ p.currentToken.WriteByte(p.currentByte)
+ case 'n':
+ p.currentToken.WriteByte('\n')
+ case '"':
+ p.currentToken.WriteByte('"')
+ default:
+ p.parseError(fmt.Sprintf("invalid escape sequence '\\%c'", p.currentByte))
+ return
+ }
+ escaped = false
+ } else {
+ switch p.currentByte {
+ case '"':
+ quoted = !quoted
+ if !quoted {
+ p.currentByte, p.err = p.buf.ReadByte()
+ return
+ }
+ case '\n':
+ p.parseError(fmt.Sprintf("metric name %q contains unescaped new-line", p.currentToken.String()))
+ return
+ case '\\':
+ escaped = true
+ default:
+ p.currentToken.WriteByte(p.currentByte)
+ }
+ }
p.currentByte, p.err = p.buf.ReadByte()
- if p.err != nil || !isValidMetricNameContinuation(p.currentByte) {
+ if !isValidMetricNameContinuation(p.currentByte, quoted) || (!quoted && p.currentByte == ' ') {
return
}
}
@@ -622,13 +743,45 @@
// but not into p.currentToken.
func (p *TextParser) readTokenAsLabelName() {
p.currentToken.Reset()
+ // A UTF-8 label name must be quoted and may have escaped characters.
+ quoted := false
+ escaped := false
if !isValidLabelNameStart(p.currentByte) {
return
}
- for {
- p.currentToken.WriteByte(p.currentByte)
+ for p.err == nil {
+ if escaped {
+ switch p.currentByte {
+ case '\\':
+ p.currentToken.WriteByte(p.currentByte)
+ case 'n':
+ p.currentToken.WriteByte('\n')
+ case '"':
+ p.currentToken.WriteByte('"')
+ default:
+ p.parseError(fmt.Sprintf("invalid escape sequence '\\%c'", p.currentByte))
+ return
+ }
+ escaped = false
+ } else {
+ switch p.currentByte {
+ case '"':
+ quoted = !quoted
+ if !quoted {
+ p.currentByte, p.err = p.buf.ReadByte()
+ return
+ }
+ case '\n':
+ p.parseError(fmt.Sprintf("label name %q contains unescaped new-line", p.currentToken.String()))
+ return
+ case '\\':
+ escaped = true
+ default:
+ p.currentToken.WriteByte(p.currentByte)
+ }
+ }
p.currentByte, p.err = p.buf.ReadByte()
- if p.err != nil || !isValidLabelNameContinuation(p.currentByte) {
+ if !isValidLabelNameContinuation(p.currentByte, quoted) || (!quoted && p.currentByte == '=') {
return
}
}
@@ -654,6 +807,7 @@
p.currentToken.WriteByte('\n')
default:
p.parseError(fmt.Sprintf("invalid escape sequence '\\%c'", p.currentByte))
+ p.currentLabelPairs = nil
return
}
escaped = false
@@ -679,6 +833,10 @@
p.currentIsHistogramCount = false
p.currentIsHistogramSum = false
name := p.currentToken.String()
+ if !p.scheme.IsValidMetricName(name) {
+ p.parseError(fmt.Sprintf("invalid metric name %q", name))
+ return
+ }
if p.currentMF = p.metricFamiliesByName[name]; p.currentMF != nil {
return
}
@@ -712,19 +870,19 @@
}
func isValidLabelNameStart(b byte) bool {
- return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_'
+ return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || b == '"'
}
-func isValidLabelNameContinuation(b byte) bool {
- return isValidLabelNameStart(b) || (b >= '0' && b <= '9')
+func isValidLabelNameContinuation(b byte, quoted bool) bool {
+ return isValidLabelNameStart(b) || (b >= '0' && b <= '9') || (quoted && utf8.ValidString(string(b)))
}
func isValidMetricNameStart(b byte) bool {
return isValidLabelNameStart(b) || b == ':'
}
-func isValidMetricNameContinuation(b byte) bool {
- return isValidLabelNameContinuation(b) || b == ':'
+func isValidMetricNameContinuation(b byte, quoted bool) bool {
+ return isValidLabelNameContinuation(b, quoted) || b == ':'
}
func isBlankOrTab(b byte) bool {
@@ -769,7 +927,7 @@
func parseFloat(s string) (float64, error) {
if strings.ContainsAny(s, "pP_") {
- return 0, fmt.Errorf("unsupported character in float")
+ return 0, errors.New("unsupported character in float")
}
return strconv.ParseFloat(s, 64)
}
diff --git a/vendor/github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg/README.txt b/vendor/github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg/README.txt
deleted file mode 100644
index 7723656..0000000
--- a/vendor/github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg/README.txt
+++ /dev/null
@@ -1,67 +0,0 @@
-PACKAGE
-
-package goautoneg
-import "bitbucket.org/ww/goautoneg"
-
-HTTP Content-Type Autonegotiation.
-
-The functions in this package implement the behaviour specified in
-http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
-
-Copyright (c) 2011, Open Knowledge Foundation Ltd.
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
- Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
-
- Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in
- the documentation and/or other materials provided with the
- distribution.
-
- Neither the name of the Open Knowledge Foundation Ltd. nor the
- names of its contributors may be used to endorse or promote
- products derived from this software without specific prior written
- permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-
-FUNCTIONS
-
-func Negotiate(header string, alternatives []string) (content_type string)
-Negotiate the most appropriate content_type given the accept header
-and a list of alternatives.
-
-func ParseAccept(header string) (accept []Accept)
-Parse an Accept Header string returning a sorted list
-of clauses
-
-
-TYPES
-
-type Accept struct {
- Type, SubType string
- Q float32
- Params map[string]string
-}
-Structure to represent a clause in an HTTP Accept Header
-
-
-SUBDIRECTORIES
-
- .hg
diff --git a/vendor/github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg/autoneg.go b/vendor/github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg/autoneg.go
deleted file mode 100644
index 26e9228..0000000
--- a/vendor/github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg/autoneg.go
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
-Copyright (c) 2011, Open Knowledge Foundation Ltd.
-All rights reserved.
-
-HTTP Content-Type Autonegotiation.
-
-The functions in this package implement the behaviour specified in
-http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
- Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
-
- Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in
- the documentation and/or other materials provided with the
- distribution.
-
- Neither the name of the Open Knowledge Foundation Ltd. nor the
- names of its contributors may be used to endorse or promote
- products derived from this software without specific prior written
- permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-
-*/
-package goautoneg
-
-import (
- "sort"
- "strconv"
- "strings"
-)
-
-// Structure to represent a clause in an HTTP Accept Header
-type Accept struct {
- Type, SubType string
- Q float64
- Params map[string]string
-}
-
-// For internal use, so that we can use the sort interface
-type accept_slice []Accept
-
-func (accept accept_slice) Len() int {
- slice := []Accept(accept)
- return len(slice)
-}
-
-func (accept accept_slice) Less(i, j int) bool {
- slice := []Accept(accept)
- ai, aj := slice[i], slice[j]
- if ai.Q > aj.Q {
- return true
- }
- if ai.Type != "*" && aj.Type == "*" {
- return true
- }
- if ai.SubType != "*" && aj.SubType == "*" {
- return true
- }
- return false
-}
-
-func (accept accept_slice) Swap(i, j int) {
- slice := []Accept(accept)
- slice[i], slice[j] = slice[j], slice[i]
-}
-
-// Parse an Accept Header string returning a sorted list
-// of clauses
-func ParseAccept(header string) (accept []Accept) {
- parts := strings.Split(header, ",")
- accept = make([]Accept, 0, len(parts))
- for _, part := range parts {
- part := strings.Trim(part, " ")
-
- a := Accept{}
- a.Params = make(map[string]string)
- a.Q = 1.0
-
- mrp := strings.Split(part, ";")
-
- media_range := mrp[0]
- sp := strings.Split(media_range, "/")
- a.Type = strings.Trim(sp[0], " ")
-
- switch {
- case len(sp) == 1 && a.Type == "*":
- a.SubType = "*"
- case len(sp) == 2:
- a.SubType = strings.Trim(sp[1], " ")
- default:
- continue
- }
-
- if len(mrp) == 1 {
- accept = append(accept, a)
- continue
- }
-
- for _, param := range mrp[1:] {
- sp := strings.SplitN(param, "=", 2)
- if len(sp) != 2 {
- continue
- }
- token := strings.Trim(sp[0], " ")
- if token == "q" {
- a.Q, _ = strconv.ParseFloat(sp[1], 32)
- } else {
- a.Params[token] = strings.Trim(sp[1], " ")
- }
- }
-
- accept = append(accept, a)
- }
-
- slice := accept_slice(accept)
- sort.Sort(slice)
-
- return
-}
-
-// Negotiate the most appropriate content_type given the accept header
-// and a list of alternatives.
-func Negotiate(header string, alternatives []string) (content_type string) {
- asp := make([][]string, 0, len(alternatives))
- for _, ctype := range alternatives {
- asp = append(asp, strings.SplitN(ctype, "/", 2))
- }
- for _, clause := range ParseAccept(header) {
- for i, ctsp := range asp {
- if clause.Type == ctsp[0] && clause.SubType == ctsp[1] {
- content_type = alternatives[i]
- return
- }
- if clause.Type == ctsp[0] && clause.SubType == "*" {
- content_type = alternatives[i]
- return
- }
- if clause.Type == "*" && clause.SubType == "*" {
- content_type = alternatives[i]
- return
- }
- }
- }
- return
-}
diff --git a/vendor/github.com/prometheus/common/model/alert.go b/vendor/github.com/prometheus/common/model/alert.go
index 35e739c..460f554 100644
--- a/vendor/github.com/prometheus/common/model/alert.go
+++ b/vendor/github.com/prometheus/common/model/alert.go
@@ -14,6 +14,7 @@
package model
import (
+ "errors"
"fmt"
"time"
)
@@ -64,7 +65,7 @@
return a.ResolvedAt(time.Now())
}
-// ResolvedAt returns true off the activity interval ended before
+// ResolvedAt returns true iff the activity interval ended before
// the given timestamp.
func (a *Alert) ResolvedAt(ts time.Time) bool {
if a.EndsAt.IsZero() {
@@ -75,7 +76,12 @@
// Status returns the status of the alert.
func (a *Alert) Status() AlertStatus {
- if a.Resolved() {
+ return a.StatusAt(time.Now())
+}
+
+// StatusAt returns the status of the alert at the given timestamp.
+func (a *Alert) StatusAt(ts time.Time) AlertStatus {
+ if a.ResolvedAt(ts) {
return AlertResolved
}
return AlertFiring
@@ -84,19 +90,19 @@
// Validate checks whether the alert data is inconsistent.
func (a *Alert) Validate() error {
if a.StartsAt.IsZero() {
- return fmt.Errorf("start time missing")
+ return errors.New("start time missing")
}
if !a.EndsAt.IsZero() && a.EndsAt.Before(a.StartsAt) {
- return fmt.Errorf("start time must be before end time")
+ return errors.New("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")
+ return errors.New("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
}
@@ -127,6 +133,17 @@
return false
}
+// HasFiringAt returns true iff one of the alerts is not resolved
+// at the time ts.
+func (as Alerts) HasFiringAt(ts time.Time) bool {
+ for _, a := range as {
+ if !a.ResolvedAt(ts) {
+ return true
+ }
+ }
+ return false
+}
+
// Status returns StatusFiring iff at least one of the alerts is firing.
func (as Alerts) Status() AlertStatus {
if as.HasFiring() {
@@ -134,3 +151,12 @@
}
return AlertResolved
}
+
+// StatusAt returns StatusFiring iff at least one of the alerts is firing
+// at the time ts.
+func (as Alerts) StatusAt(ts time.Time) AlertStatus {
+ if as.HasFiringAt(ts) {
+ return AlertFiring
+ }
+ return AlertResolved
+}
diff --git a/vendor/github.com/prometheus/common/model/labels.go b/vendor/github.com/prometheus/common/model/labels.go
index ef89563..dfeb34b 100644
--- a/vendor/github.com/prometheus/common/model/labels.go
+++ b/vendor/github.com/prometheus/common/model/labels.go
@@ -22,7 +22,7 @@
)
const (
- // AlertNameLabel is the name of the label containing the an alert's name.
+ // AlertNameLabel is the name of the label containing the alert's name.
AlertNameLabel = "alertname"
// ExportedLabelPrefix is the prefix to prepend to the label names present in
@@ -32,6 +32,12 @@
// MetricNameLabel is the label name indicating the metric name of a
// timeseries.
MetricNameLabel = "__name__"
+ // MetricTypeLabel is the label name indicating the metric type of
+ // timeseries as per the PROM-39 proposal.
+ MetricTypeLabel = "__type__"
+ // MetricUnitLabel is the label name indicating the metric unit of
+ // timeseries as per the PROM-39 proposal.
+ MetricUnitLabel = "__unit__"
// SchemeLabel is the name of the label that holds the scheme on which to
// scrape a target.
@@ -97,19 +103,24 @@
// 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 the name matches the pattern of LabelNameRE when
+// NameValidationScheme is set to LegacyValidation, or valid UTF-8 if
+// NameValidationScheme is set to UTF8Validation.
+//
+// Deprecated: This method should not be used and may be removed in the future.
+// Use [ValidationScheme.IsValidLabelName] instead.
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
- }
- }
- return true
+ return NameValidationScheme.IsValidLabelName(string(ln))
+}
+
+// IsValidLegacy returns true iff name matches the pattern of LabelNameRE for
+// legacy names. It does not use LabelNameRE for the check but a much faster
+// hardcoded implementation.
+//
+// Deprecated: This method should not be used and may be removed in the future.
+// Use [LegacyValidation.IsValidLabelName] instead.
+func (ln LabelName) IsValidLegacy() bool {
+ return LegacyValidation.IsValidLabelName(string(ln))
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
@@ -164,7 +175,7 @@
// 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/labelset.go b/vendor/github.com/prometheus/common/model/labelset.go
index 6eda08a..9de47b2 100644
--- a/vendor/github.com/prometheus/common/model/labelset.go
+++ b/vendor/github.com/prometheus/common/model/labelset.go
@@ -17,7 +17,6 @@
"encoding/json"
"fmt"
"sort"
- "strings"
)
// A LabelSet is a collection of LabelName and LabelValue pairs. The LabelSet
@@ -115,10 +114,10 @@
}
// Merge is a helper function to non-destructively merge two label sets.
-func (l LabelSet) Merge(other LabelSet) LabelSet {
- result := make(LabelSet, len(l))
+func (ls LabelSet) Merge(other LabelSet) LabelSet {
+ result := make(LabelSet, len(ls))
- for k, v := range l {
+ for k, v := range ls {
result[k] = v
}
@@ -129,16 +128,6 @@
return result
}
-func (l LabelSet) String() string {
- lstrs := make([]string, 0, len(l))
- for l, v := range l {
- lstrs = append(lstrs, fmt.Sprintf("%s=%q", l, v))
- }
-
- sort.Strings(lstrs)
- return fmt.Sprintf("{%s}", strings.Join(lstrs, ", "))
-}
-
// Fingerprint returns the LabelSet's fingerprint.
func (ls LabelSet) Fingerprint() Fingerprint {
return labelSetToFingerprint(ls)
@@ -151,7 +140,7 @@
}
// UnmarshalJSON implements the json.Unmarshaler interface.
-func (l *LabelSet) UnmarshalJSON(b []byte) error {
+func (ls *LabelSet) UnmarshalJSON(b []byte) error {
var m map[LabelName]LabelValue
if err := json.Unmarshal(b, &m); err != nil {
return err
@@ -164,6 +153,6 @@
return fmt.Errorf("%q is not a valid label name", ln)
}
}
- *l = LabelSet(m)
+ *ls = LabelSet(m)
return nil
}
diff --git a/vendor/github.com/prometheus/common/model/labelset_string.go b/vendor/github.com/prometheus/common/model/labelset_string.go
new file mode 100644
index 0000000..abb2c90
--- /dev/null
+++ b/vendor/github.com/prometheus/common/model/labelset_string.go
@@ -0,0 +1,43 @@
+// Copyright 2024 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
+
+import (
+ "bytes"
+ "slices"
+ "strconv"
+)
+
+// String will look like `{foo="bar", more="less"}`. Names are sorted alphabetically.
+func (l LabelSet) String() string {
+ var lna [32]string // On stack to avoid memory allocation for sorting names.
+ labelNames := lna[:0]
+ for name := range l {
+ labelNames = append(labelNames, string(name))
+ }
+ slices.Sort(labelNames)
+ var bytea [1024]byte // On stack to avoid memory allocation while building the output.
+ b := bytes.NewBuffer(bytea[:0])
+ b.WriteByte('{')
+ for i, name := range labelNames {
+ if i > 0 {
+ b.WriteString(", ")
+ }
+ b.WriteString(name)
+ b.WriteByte('=')
+ b.Write(strconv.AppendQuote(b.AvailableBuffer(), string(l[LabelName(name)])))
+ }
+ b.WriteByte('}')
+ return b.String()
+}
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 0000000..447ab8a
--- /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 00804b7..3feebf3 100644
--- a/vendor/github.com/prometheus/common/model/metric.go
+++ b/vendor/github.com/prometheus/common/model/metric.go
@@ -14,19 +14,242 @@
package model
import (
+ "encoding/json"
+ "errors"
"fmt"
"regexp"
"sort"
+ "strconv"
"strings"
+ "unicode/utf8"
+
+ dto "github.com/prometheus/client_model/go"
+ "go.yaml.in/yaml/v2"
+ "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 global default method of the name
+ // validation to be used by all calls to IsValidMetricName() and LabelName
+ // IsValid().
+ //
+ // Deprecated: This variable should not be used and might be removed in the
+ // far future. If you wish to stick to the legacy name validation use
+ // `IsValidLegacyMetricName()` and `LabelName.IsValidLegacy()` methods
+ // instead. This variable is here as an escape hatch for emergency cases,
+ // given the recent change from `LegacyValidation` to `UTF8Validation`, e.g.,
+ // to delay UTF-8 migrations in time or aid in debugging unforeseen results of
+ // the change. In such a case, a temporary assignment to `LegacyValidation`
+ // value in the `init()` function in your main.go or so, could be considered.
+ //
+ // Historically we opted for a global variable for feature gating different
+ // validation schemes in operations that were not otherwise easily adjustable
+ // (e.g. Labels yaml unmarshaling). That could have been a mistake, a separate
+ // Labels structure or package might have been a better choice. Given the
+ // change was made and many upgraded the common already, we live this as-is
+ // with this warning and learning for the future.
+ NameValidationScheme = UTF8Validation
+
+ // 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 should not be set to the NoEscaping value. That string
+ // is used in content negotiation to indicate that a system supports UTF-8 and
+ // has that feature enabled.
+ NameEscapingScheme = UnderscoreEscaping
)
+// ValidationScheme is a Go enum for determining how metric and label names will
+// be validated by this library.
+type ValidationScheme int
+
+const (
+ // UnsetValidation represents an undefined ValidationScheme.
+ // Should not be used in practice.
+ UnsetValidation ValidationScheme = iota
+
+ // LegacyValidation is a setting that requires that all metric and label names
+ // conform to the original Prometheus character requirements described by
+ // MetricNameRE and LabelNameRE.
+ LegacyValidation
+
+ // UTF8Validation only requires that metric and label names be valid UTF-8
+ // strings.
+ UTF8Validation
+)
+
+var _ interface {
+ yaml.Marshaler
+ yaml.Unmarshaler
+ json.Marshaler
+ json.Unmarshaler
+ fmt.Stringer
+} = new(ValidationScheme)
+
+// String returns the string representation of s.
+func (s ValidationScheme) String() string {
+ switch s {
+ case UnsetValidation:
+ return "unset"
+ case LegacyValidation:
+ return "legacy"
+ case UTF8Validation:
+ return "utf8"
+ default:
+ panic(fmt.Errorf("unhandled ValidationScheme: %d", s))
+ }
+}
+
+// MarshalYAML implements the yaml.Marshaler interface.
+func (s ValidationScheme) MarshalYAML() (any, error) {
+ switch s {
+ case UnsetValidation:
+ return "", nil
+ case LegacyValidation, UTF8Validation:
+ return s.String(), nil
+ default:
+ panic(fmt.Errorf("unhandled ValidationScheme: %d", s))
+ }
+}
+
+// UnmarshalYAML implements the yaml.Unmarshaler interface.
+func (s *ValidationScheme) UnmarshalYAML(unmarshal func(any) error) error {
+ var scheme string
+ if err := unmarshal(&scheme); err != nil {
+ return err
+ }
+ return s.Set(scheme)
+}
+
+// MarshalJSON implements the json.Marshaler interface.
+func (s ValidationScheme) MarshalJSON() ([]byte, error) {
+ switch s {
+ case UnsetValidation:
+ return json.Marshal("")
+ case UTF8Validation, LegacyValidation:
+ return json.Marshal(s.String())
+ default:
+ return nil, fmt.Errorf("unhandled ValidationScheme: %d", s)
+ }
+}
+
+// UnmarshalJSON implements the json.Unmarshaler interface.
+func (s *ValidationScheme) UnmarshalJSON(bytes []byte) error {
+ var repr string
+ if err := json.Unmarshal(bytes, &repr); err != nil {
+ return err
+ }
+ return s.Set(repr)
+}
+
+// Set implements the pflag.Value interface.
+func (s *ValidationScheme) Set(text string) error {
+ switch text {
+ case "":
+ // Don't change the value.
+ case LegacyValidation.String():
+ *s = LegacyValidation
+ case UTF8Validation.String():
+ *s = UTF8Validation
+ default:
+ return fmt.Errorf("unrecognized ValidationScheme: %q", text)
+ }
+ return nil
+}
+
+// IsValidMetricName returns whether metricName is valid according to s.
+func (s ValidationScheme) IsValidMetricName(metricName string) bool {
+ switch s {
+ case LegacyValidation:
+ if len(metricName) == 0 {
+ return false
+ }
+ for i, b := range metricName {
+ if !isValidLegacyRune(b, i) {
+ return false
+ }
+ }
+ return true
+ case UTF8Validation:
+ if len(metricName) == 0 {
+ return false
+ }
+ return utf8.ValidString(metricName)
+ default:
+ panic(fmt.Sprintf("Invalid name validation scheme requested: %s", s.String()))
+ }
+}
+
+// IsValidLabelName returns whether labelName is valid according to s.
+func (s ValidationScheme) IsValidLabelName(labelName string) bool {
+ switch s {
+ case LegacyValidation:
+ if len(labelName) == 0 {
+ return false
+ }
+ for i, b := range labelName {
+ // TODO: Apply De Morgan's law. Make sure there are tests for this.
+ if !((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || (b >= '0' && b <= '9' && i > 0)) { //nolint:staticcheck
+ return false
+ }
+ }
+ return true
+ case UTF8Validation:
+ if len(labelName) == 0 {
+ return false
+ }
+ return utf8.ValidString(labelName)
+ default:
+ panic(fmt.Sprintf("Invalid name validation scheme requested: %s", s))
+ }
+}
+
+// Type implements the pflag.Value interface.
+func (ValidationScheme) Type() string {
+ return "validationScheme"
+}
+
+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 +309,285 @@
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.
+//
+// Deprecated: This function should not be used and might be removed in the future.
+// Use [ValidationScheme.IsValidMetricName] instead.
+func IsValidMetricName(n LabelValue) bool {
+ return NameValidationScheme.IsValidMetricName(string(n))
+}
+
+// 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 {
- if len(n) == 0 {
- return false
+//
+// Deprecated: This function should not be used and might be removed in the future.
+// Use [LegacyValidation.IsValidMetricName] instead.
+func IsValidLegacyMetricName(n string) bool {
+ return LegacyValidation.IsValidMetricName(n)
+}
+
+// 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
}
- for i, b := range n {
- if !((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || b == ':' || (b >= '0' && b <= '9' && i > 0)) {
- return false
+
+ if scheme == NoEscaping {
+ return v
+ }
+
+ out := &dto.MetricFamily{
+ Help: v.Help,
+ Type: v.Type,
+ Unit: v.Unit,
+ }
+
+ // If the name is nil, copy as-is, don't try to escape.
+ if v.Name == nil || IsValidLegacyMetricName(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(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(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(l.GetValue()) {
+ return true
+ }
+ if !IsValidLegacyMetricName(l.GetName()) {
+ return true
}
}
- return true
+ return false
+}
+
+// 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(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 {
+ switch {
+ case b == '_':
+ escaped.WriteString("__")
+ case b == '.':
+ escaped.WriteString("_dot_")
+ case isValidLegacyRune(b, i):
+ escaped.WriteRune(b)
+ default:
+ escaped.WriteString("__")
+ }
+ }
+ return escaped.String()
+ case ValueEncodingEscaping:
+ if IsValidLegacyMetricName(name) {
+ return name
+ }
+ escaped.WriteString("U__")
+ for i, b := range name {
+ switch {
+ case b == '_':
+ escaped.WriteString("__")
+ case isValidLegacyRune(b, i):
+ escaped.WriteRune(b)
+ case !utf8.ValidRune(b):
+ escaped.WriteString("_FFFD_")
+ default:
+ escaped.WriteRune('_')
+ escaped.WriteString(strconv.FormatInt(int64(b), 16))
+ 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 based on the MaxRune
+ // value of '\U0010FFFF'.
+ if j >= 6 {
+ 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
+ switch {
+ case r >= '0' && r <= '9':
+ utf8Val += uint(r) - '0'
+ case r >= 'a' && r <= 'f':
+ utf8Val += uint(r) - 'a' + 10
+ default:
+ 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, errors.New("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", s)
+ }
}
diff --git a/vendor/github.com/prometheus/common/model/signature.go b/vendor/github.com/prometheus/common/model/signature.go
index 8762b13..dc8a002 100644
--- a/vendor/github.com/prometheus/common/model/signature.go
+++ b/vendor/github.com/prometheus/common/model/signature.go
@@ -22,10 +22,8 @@
// 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 bb99889..8f91a97 100644
--- a/vendor/github.com/prometheus/common/model/silence.go
+++ b/vendor/github.com/prometheus/common/model/silence.go
@@ -15,6 +15,7 @@
import (
"encoding/json"
+ "errors"
"fmt"
"regexp"
"time"
@@ -34,7 +35,7 @@
}
if len(m.Name) == 0 {
- return fmt.Errorf("label name in matcher must not be empty")
+ return errors.New("label name in matcher must not be empty")
}
if m.IsRegex {
if _, err := regexp.Compile(m.Value); err != nil {
@@ -77,30 +78,30 @@
// Validate returns true iff all fields of the silence have valid values.
func (s *Silence) Validate() error {
if len(s.Matchers) == 0 {
- return fmt.Errorf("at least one matcher required")
+ return errors.New("at least one matcher required")
}
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() {
- return fmt.Errorf("start time missing")
+ return errors.New("start time missing")
}
if s.EndsAt.IsZero() {
- return fmt.Errorf("end time missing")
+ return errors.New("end time missing")
}
if s.EndsAt.Before(s.StartsAt) {
- return fmt.Errorf("start time must be before end time")
+ return errors.New("start time must be before end time")
}
if s.CreatedBy == "" {
- return fmt.Errorf("creator information missing")
+ return errors.New("creator information missing")
}
if s.Comment == "" {
- return fmt.Errorf("comment missing")
+ return errors.New("comment missing")
}
if s.CreatedAt.IsZero() {
- return fmt.Errorf("creation timestamp missing")
+ return errors.New("creation timestamp missing")
}
return nil
}
diff --git a/vendor/github.com/prometheus/common/model/time.go b/vendor/github.com/prometheus/common/model/time.go
index 7f67b16..1730b0f 100644
--- a/vendor/github.com/prometheus/common/model/time.go
+++ b/vendor/github.com/prometheus/common/model/time.go
@@ -18,7 +18,6 @@
"errors"
"fmt"
"math"
- "regexp"
"strconv"
"strings"
"time"
@@ -127,14 +126,14 @@
p := strings.Split(string(b), ".")
switch len(p) {
case 1:
- v, err := strconv.ParseInt(string(p[0]), 10, 64)
+ v, err := strconv.ParseInt(p[0], 10, 64)
if err != nil {
return err
}
*t = Time(v * second)
case 2:
- v, err := strconv.ParseInt(string(p[0]), 10, 64)
+ v, err := strconv.ParseInt(p[0], 10, 64)
if err != nil {
return err
}
@@ -144,7 +143,7 @@
if prec < 0 {
p[1] = p[1][:dotPrecision]
} else if prec > 0 {
- p[1] = p[1] + strings.Repeat("0", prec)
+ p[1] += strings.Repeat("0", prec)
}
va, err := strconv.ParseInt(p[1], 10, 32)
@@ -171,77 +170,120 @@
// This type should not propagate beyond the scope of input/output processing.
type Duration time.Duration
-// Set implements pflag/flag.Value
+// Set implements pflag/flag.Value.
func (d *Duration) Set(s string) error {
var err error
*d, err = ParseDuration(s)
return err
}
-// Type implements pflag.Value
-func (d *Duration) Type() string {
+// Type implements pflag.Value.
+func (*Duration) Type() string {
return "duration"
}
-var durationRE = regexp.MustCompile("^(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?$")
+func isdigit(c byte) bool { return c >= '0' && c <= '9' }
+
+// Units are required to go in order from biggest to smallest.
+// This guards against confusion from "1m1d" being 1 minute + 1 day, not 1 month + 1 day.
+var unitMap = map[string]struct {
+ pos int
+ mult uint64
+}{
+ "ms": {7, uint64(time.Millisecond)},
+ "s": {6, uint64(time.Second)},
+ "m": {5, uint64(time.Minute)},
+ "h": {4, uint64(time.Hour)},
+ "d": {3, uint64(24 * time.Hour)},
+ "w": {2, uint64(7 * 24 * time.Hour)},
+ "y": {1, uint64(365 * 24 * time.Hour)},
+}
// ParseDuration parses a string into a time.Duration, assuming that a year
// always has 365d, a week always has 7d, and a day always has 24h.
-func ParseDuration(durationStr string) (Duration, error) {
- switch durationStr {
+// Negative durations are not supported.
+func ParseDuration(s string) (Duration, error) {
+ switch s {
case "0":
// Allow 0 without a unit.
return 0, nil
case "":
- return 0, fmt.Errorf("empty duration string")
+ return 0, errors.New("empty duration string")
}
- matches := durationRE.FindStringSubmatch(durationStr)
- if matches == nil {
- return 0, fmt.Errorf("not a valid duration string: %q", durationStr)
- }
- var dur time.Duration
- // Parse the match at pos `pos` in the regex and use `mult` to turn that
- // into ms, then add that value to the total parsed duration.
- var overflowErr error
- m := func(pos int, mult time.Duration) {
- if matches[pos] == "" {
- return
+ orig := s
+ var dur uint64
+ lastUnitPos := 0
+
+ for s != "" {
+ if !isdigit(s[0]) {
+ return 0, fmt.Errorf("not a valid duration string: %q", orig)
}
- n, _ := strconv.Atoi(matches[pos])
+ // Consume [0-9]*
+ i := 0
+ for ; i < len(s) && isdigit(s[i]); i++ {
+ }
+ v, err := strconv.ParseUint(s[:i], 10, 0)
+ if err != nil {
+ return 0, fmt.Errorf("not a valid duration string: %q", orig)
+ }
+ s = s[i:]
+ // Consume unit.
+ for i = 0; i < len(s) && !isdigit(s[i]); i++ {
+ }
+ if i == 0 {
+ return 0, fmt.Errorf("not a valid duration string: %q", orig)
+ }
+ u := s[:i]
+ s = s[i:]
+ unit, ok := unitMap[u]
+ if !ok {
+ return 0, fmt.Errorf("unknown unit %q in duration %q", u, orig)
+ }
+ if unit.pos <= lastUnitPos { // Units must go in order from biggest to smallest.
+ return 0, fmt.Errorf("not a valid duration string: %q", orig)
+ }
+ lastUnitPos = unit.pos
// Check if the provided duration overflows time.Duration (> ~ 290years).
- if n > int((1<<63-1)/mult/time.Millisecond) {
- overflowErr = errors.New("duration out of range")
+ if v > 1<<63/unit.mult {
+ return 0, errors.New("duration out of range")
}
- d := time.Duration(n) * time.Millisecond
- dur += d * mult
-
- if dur < 0 {
- overflowErr = errors.New("duration out of range")
+ dur += v * unit.mult
+ if dur > 1<<63-1 {
+ return 0, errors.New("duration out of range")
}
}
- m(2, 1000*60*60*24*365) // y
- m(4, 1000*60*60*24*7) // w
- m(6, 1000*60*60*24) // d
- m(8, 1000*60*60) // h
- m(10, 1000*60) // m
- m(12, 1000) // s
- m(14, 1) // ms
+ return Duration(dur), nil
+}
- return Duration(dur), overflowErr
+// ParseDurationAllowNegative is like ParseDuration but also accepts negative durations.
+func ParseDurationAllowNegative(s string) (Duration, error) {
+ if s == "" || s[0] != '-' {
+ return ParseDuration(s)
+ }
+
+ d, err := ParseDuration(s[1:])
+
+ return -d, err
}
func (d Duration) String() string {
var (
- ms = int64(time.Duration(d) / time.Millisecond)
- r = ""
+ ms = int64(time.Duration(d) / time.Millisecond)
+ r = ""
+ sign = ""
)
+
if ms == 0 {
return "0s"
}
+ if ms < 0 {
+ sign, ms = "-", -ms
+ }
+
f := func(unit string, mult int64, exact bool) {
if exact && ms%mult != 0 {
return
@@ -263,7 +305,7 @@
f("s", 1000, false)
f("ms", 1, false)
- return r
+ return sign + r
}
// MarshalJSON implements the json.Marshaler interface.
diff --git a/vendor/github.com/prometheus/common/model/value.go b/vendor/github.com/prometheus/common/model/value.go
index c9d8fb1..a9995a3 100644
--- a/vendor/github.com/prometheus/common/model/value.go
+++ b/vendor/github.com/prometheus/common/model/value.go
@@ -16,104 +16,26 @@
import (
"encoding/json"
"fmt"
- "math"
"sort"
"strconv"
"strings"
)
-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}
+// 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}
- // 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}
-)
-
-// A SampleValue is a representation of a value for a given sample at a given
-// time.
-type SampleValue float64
-
-// MarshalJSON implements json.Marshaler.
-func (v SampleValue) MarshalJSON() ([]byte, error) {
- return json.Marshal(v.String())
-}
-
-// UnmarshalJSON implements json.Unmarshaler.
-func (v *SampleValue) UnmarshalJSON(b []byte) error {
- if len(b) < 2 || b[0] != '"' || b[len(b)-1] != '"' {
- return fmt.Errorf("sample value must be a quoted string")
- }
- f, err := strconv.ParseFloat(string(b[1:len(b)-1]), 64)
- if err != nil {
- return err
- }
- *v = SampleValue(f)
- return nil
-}
-
-// Equal returns true if the value of v and o is equal or if both are NaN. Note
-// that v==o is false if both are NaN. If you want the conventional float
-// behavior, use == to compare two SampleValues.
-func (v SampleValue) Equal(o SampleValue) bool {
- if v == o {
- return true
- }
- return math.IsNaN(float64(v)) && math.IsNaN(float64(o))
-}
-
-func (v SampleValue) String() string {
- return strconv.FormatFloat(float64(v), 'f', -1, 64)
-}
-
-// SamplePair pairs a SampleValue with a Timestamp.
-type SamplePair struct {
- Timestamp Time
- Value SampleValue
-}
-
-// MarshalJSON implements json.Marshaler.
-func (s SamplePair) MarshalJSON() ([]byte, error) {
- t, err := json.Marshal(s.Timestamp)
- if err != nil {
- return nil, err
- }
- v, err := json.Marshal(s.Value)
- if err != nil {
- return nil, err
- }
- return []byte(fmt.Sprintf("[%s,%s]", t, v)), nil
-}
-
-// UnmarshalJSON implements json.Unmarshaler.
-func (s *SamplePair) UnmarshalJSON(b []byte) error {
- v := [...]json.Unmarshaler{&s.Timestamp, &s.Value}
- return json.Unmarshal(b, &v)
-}
-
-// Equal returns true if this SamplePair and o have equal Values and equal
-// Timestamps. The semantics of Value equality is defined by SampleValue.Equal.
-func (s *SamplePair) Equal(o *SamplePair) bool {
- return s == o || (s.Value.Equal(o.Value) && s.Timestamp.Equal(o.Timestamp))
-}
-
-func (s SamplePair) String() string {
- return fmt.Sprintf("%s @[%s]", s.Value, s.Timestamp)
-}
-
-// Sample is a sample pair associated with a metric.
+// 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
+// field is used, otherwise it should be ignored.
type Sample struct {
- Metric Metric `json:"metric"`
- Value SampleValue `json:"value"`
- Timestamp Time `json:"timestamp"`
+ Metric Metric `json:"metric"`
+ Value SampleValue `json:"value"`
+ Timestamp Time `json:"timestamp"`
+ Histogram *SampleHistogram `json:"histogram"`
}
// Equal compares first the metrics, then the timestamp, then the value. The
@@ -129,11 +51,19 @@
if !s.Timestamp.Equal(o.Timestamp) {
return false
}
-
+ if s.Histogram != nil {
+ return s.Histogram.Equal(o.Histogram)
+ }
return s.Value.Equal(o.Value)
}
func (s Sample) String() string {
+ if s.Histogram != nil {
+ return fmt.Sprintf("%s => %s", s.Metric, SampleHistogramPair{
+ Timestamp: s.Timestamp,
+ Histogram: s.Histogram,
+ })
+ }
return fmt.Sprintf("%s => %s", s.Metric, SamplePair{
Timestamp: s.Timestamp,
Value: s.Value,
@@ -142,6 +72,19 @@
// MarshalJSON implements json.Marshaler.
func (s Sample) MarshalJSON() ([]byte, error) {
+ if s.Histogram != nil {
+ v := struct {
+ Metric Metric `json:"metric"`
+ Histogram SampleHistogramPair `json:"histogram"`
+ }{
+ Metric: s.Metric,
+ Histogram: SampleHistogramPair{
+ Timestamp: s.Timestamp,
+ Histogram: s.Histogram,
+ },
+ }
+ return json.Marshal(&v)
+ }
v := struct {
Metric Metric `json:"metric"`
Value SamplePair `json:"value"`
@@ -152,21 +95,25 @@
Value: s.Value,
},
}
-
return json.Marshal(&v)
}
// UnmarshalJSON implements json.Unmarshaler.
func (s *Sample) UnmarshalJSON(b []byte) error {
v := struct {
- Metric Metric `json:"metric"`
- Value SamplePair `json:"value"`
+ Metric Metric `json:"metric"`
+ Value SamplePair `json:"value"`
+ Histogram SampleHistogramPair `json:"histogram"`
}{
Metric: s.Metric,
Value: SamplePair{
Timestamp: s.Timestamp,
Value: s.Value,
},
+ Histogram: SampleHistogramPair{
+ Timestamp: s.Timestamp,
+ Histogram: s.Histogram,
+ },
}
if err := json.Unmarshal(b, &v); err != nil {
@@ -174,8 +121,13 @@
}
s.Metric = v.Metric
- s.Timestamp = v.Value.Timestamp
- s.Value = v.Value.Value
+ if v.Histogram.Histogram != nil {
+ s.Timestamp = v.Histogram.Timestamp
+ s.Histogram = v.Histogram.Histogram
+ } else {
+ s.Timestamp = v.Value.Timestamp
+ s.Value = v.Value.Value
+ }
return nil
}
@@ -221,80 +173,77 @@
// SampleStream is a stream of Values belonging to an attached COWMetric.
type SampleStream struct {
- Metric Metric `json:"metric"`
- Values []SamplePair `json:"values"`
+ Metric Metric `json:"metric"`
+ Values []SamplePair `json:"values"`
+ Histograms []SampleHistogramPair `json:"histograms"`
}
func (ss SampleStream) String() string {
- vals := make([]string, len(ss.Values))
+ valuesLength := len(ss.Values)
+ vals := make([]string, valuesLength+len(ss.Histograms))
for i, v := range ss.Values {
vals[i] = v.String()
}
+ for i, v := range ss.Histograms {
+ vals[i+valuesLength] = v.String()
+ }
return fmt.Sprintf("%s =>\n%s", ss.Metric, strings.Join(vals, "\n"))
}
-// Value is a generic interface for values resulting from a query evaluation.
-type Value interface {
- Type() ValueType
- String() string
+func (ss SampleStream) MarshalJSON() ([]byte, error) {
+ switch {
+ case len(ss.Histograms) > 0 && len(ss.Values) > 0:
+ v := struct {
+ Metric Metric `json:"metric"`
+ Values []SamplePair `json:"values"`
+ Histograms []SampleHistogramPair `json:"histograms"`
+ }{
+ Metric: ss.Metric,
+ Values: ss.Values,
+ Histograms: ss.Histograms,
+ }
+ return json.Marshal(&v)
+ case len(ss.Histograms) > 0:
+ v := struct {
+ Metric Metric `json:"metric"`
+ Histograms []SampleHistogramPair `json:"histograms"`
+ }{
+ Metric: ss.Metric,
+ Histograms: ss.Histograms,
+ }
+ return json.Marshal(&v)
+ default:
+ v := struct {
+ Metric Metric `json:"metric"`
+ Values []SamplePair `json:"values"`
+ }{
+ Metric: ss.Metric,
+ Values: ss.Values,
+ }
+ return json.Marshal(&v)
+ }
}
-func (Matrix) Type() ValueType { return ValMatrix }
-func (Vector) Type() ValueType { return ValVector }
-func (*Scalar) Type() ValueType { return ValScalar }
-func (*String) Type() ValueType { return ValString }
+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,
+ }
-type ValueType int
-
-const (
- ValNone ValueType = iota
- ValScalar
- ValVector
- ValMatrix
- ValString
-)
-
-// MarshalJSON implements json.Marshaler.
-func (et ValueType) MarshalJSON() ([]byte, error) {
- return json.Marshal(et.String())
-}
-
-func (et *ValueType) UnmarshalJSON(b []byte) error {
- var s string
- if err := json.Unmarshal(b, &s); err != nil {
+ if err := json.Unmarshal(b, &v); err != nil {
return err
}
- switch s {
- case "<ValNone>":
- *et = ValNone
- case "scalar":
- *et = ValScalar
- case "vector":
- *et = ValVector
- case "matrix":
- *et = ValMatrix
- case "string":
- *et = ValString
- default:
- return fmt.Errorf("unknown value type %q", s)
- }
- return nil
-}
-func (e ValueType) String() string {
- switch e {
- case ValNone:
- return "<ValNone>"
- case ValScalar:
- return "scalar"
- case ValVector:
- return "vector"
- case ValMatrix:
- return "matrix"
- case ValString:
- return "string"
- }
- panic("ValueType.String: unhandled value type")
+ ss.Metric = v.Metric
+ ss.Values = v.Values
+ ss.Histograms = v.Histograms
+
+ return nil
}
// Scalar is a scalar value evaluated at the set timestamp.
@@ -310,7 +259,7 @@
// MarshalJSON implements json.Marshaler.
func (s Scalar) MarshalJSON() ([]byte, error) {
v := strconv.FormatFloat(float64(s.Value), 'f', -1, 64)
- return json.Marshal([...]interface{}{s.Timestamp, string(v)})
+ return json.Marshal([...]interface{}{s.Timestamp, v})
}
// UnmarshalJSON implements json.Unmarshaler.
@@ -324,7 +273,7 @@
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
@@ -401,9 +350,9 @@
func (m Matrix) Less(i, j int) bool { return m[i].Metric.Before(m[j].Metric) }
func (m Matrix) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
-func (mat Matrix) String() string {
- matCp := make(Matrix, len(mat))
- copy(matCp, mat)
+func (m Matrix) String() string {
+ matCp := make(Matrix, len(m))
+ copy(matCp, m)
sort.Sort(matCp)
strs := make([]string, len(matCp))
diff --git a/vendor/github.com/prometheus/common/model/value_float.go b/vendor/github.com/prometheus/common/model/value_float.go
new file mode 100644
index 0000000..6bfc757
--- /dev/null
+++ b/vendor/github.com/prometheus/common/model/value_float.go
@@ -0,0 +1,99 @@
+// Copyright 2013 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
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "math"
+ "strconv"
+)
+
+// 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.
+type SampleValue float64
+
+// MarshalJSON implements json.Marshaler.
+func (v SampleValue) MarshalJSON() ([]byte, error) {
+ return json.Marshal(v.String())
+}
+
+// UnmarshalJSON implements json.Unmarshaler.
+func (v *SampleValue) UnmarshalJSON(b []byte) error {
+ if len(b) < 2 || b[0] != '"' || b[len(b)-1] != '"' {
+ return errors.New("sample value must be a quoted string")
+ }
+ f, err := strconv.ParseFloat(string(b[1:len(b)-1]), 64)
+ if err != nil {
+ return err
+ }
+ *v = SampleValue(f)
+ return nil
+}
+
+// Equal returns true if the value of v and o is equal or if both are NaN. Note
+// that v==o is false if both are NaN. If you want the conventional float
+// behavior, use == to compare two SampleValues.
+func (v SampleValue) Equal(o SampleValue) bool {
+ if v == o {
+ return true
+ }
+ return math.IsNaN(float64(v)) && math.IsNaN(float64(o))
+}
+
+func (v SampleValue) String() string {
+ return strconv.FormatFloat(float64(v), 'f', -1, 64)
+}
+
+// SamplePair pairs a SampleValue with a Timestamp.
+type SamplePair struct {
+ Timestamp Time
+ Value SampleValue
+}
+
+func (s SamplePair) MarshalJSON() ([]byte, error) {
+ t, err := json.Marshal(s.Timestamp)
+ if err != nil {
+ return nil, err
+ }
+ v, err := json.Marshal(s.Value)
+ if err != nil {
+ return nil, err
+ }
+ return []byte(fmt.Sprintf("[%s,%s]", t, v)), nil
+}
+
+// UnmarshalJSON implements json.Unmarshaler.
+func (s *SamplePair) UnmarshalJSON(b []byte) error {
+ v := [...]json.Unmarshaler{&s.Timestamp, &s.Value}
+ return json.Unmarshal(b, &v)
+}
+
+// Equal returns true if this SamplePair and o have equal Values and equal
+// Timestamps. The semantics of Value equality is defined by SampleValue.Equal.
+func (s *SamplePair) Equal(o *SamplePair) bool {
+ return s == o || (s.Value.Equal(o.Value) && s.Timestamp.Equal(o.Timestamp))
+}
+
+func (s SamplePair) String() string {
+ return fmt.Sprintf("%s @[%s]", s.Value, s.Timestamp)
+}
diff --git a/vendor/github.com/prometheus/common/model/value_histogram.go b/vendor/github.com/prometheus/common/model/value_histogram.go
new file mode 100644
index 0000000..91ce5b7
--- /dev/null
+++ b/vendor/github.com/prometheus/common/model/value_histogram.go
@@ -0,0 +1,179 @@
+// Copyright 2013 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
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "strconv"
+ "strings"
+)
+
+type FloatString float64
+
+func (v FloatString) String() string {
+ return strconv.FormatFloat(float64(v), 'f', -1, 64)
+}
+
+func (v FloatString) MarshalJSON() ([]byte, error) {
+ return json.Marshal(v.String())
+}
+
+func (v *FloatString) UnmarshalJSON(b []byte) error {
+ if len(b) < 2 || b[0] != '"' || b[len(b)-1] != '"' {
+ return errors.New("float value must be a quoted string")
+ }
+ f, err := strconv.ParseFloat(string(b[1:len(b)-1]), 64)
+ if err != nil {
+ return err
+ }
+ *v = FloatString(f)
+ return nil
+}
+
+type HistogramBucket struct {
+ Boundaries int32
+ Lower FloatString
+ Upper FloatString
+ Count FloatString
+}
+
+func (s HistogramBucket) MarshalJSON() ([]byte, error) {
+ b, err := json.Marshal(s.Boundaries)
+ if err != nil {
+ return nil, err
+ }
+ l, err := json.Marshal(s.Lower)
+ if err != nil {
+ return nil, err
+ }
+ u, err := json.Marshal(s.Upper)
+ if err != nil {
+ return nil, err
+ }
+ c, err := json.Marshal(s.Count)
+ if err != nil {
+ return nil, err
+ }
+ return []byte(fmt.Sprintf("[%s,%s,%s,%s]", b, l, u, c)), nil
+}
+
+func (s *HistogramBucket) UnmarshalJSON(buf []byte) error {
+ tmp := []interface{}{&s.Boundaries, &s.Lower, &s.Upper, &s.Count}
+ wantLen := len(tmp)
+ if err := json.Unmarshal(buf, &tmp); err != nil {
+ return err
+ }
+ if gotLen := len(tmp); gotLen != wantLen {
+ return fmt.Errorf("wrong number of fields: %d != %d", gotLen, wantLen)
+ }
+ return nil
+}
+
+func (s *HistogramBucket) Equal(o *HistogramBucket) bool {
+ return s == o || (s.Boundaries == o.Boundaries && s.Lower == o.Lower && s.Upper == o.Upper && s.Count == o.Count)
+}
+
+func (s HistogramBucket) String() string {
+ var sb strings.Builder
+ lowerInclusive := s.Boundaries == 1 || s.Boundaries == 3
+ upperInclusive := s.Boundaries == 0 || s.Boundaries == 3
+ if lowerInclusive {
+ sb.WriteRune('[')
+ } else {
+ sb.WriteRune('(')
+ }
+ fmt.Fprintf(&sb, "%g,%g", s.Lower, s.Upper)
+ if upperInclusive {
+ sb.WriteRune(']')
+ } else {
+ sb.WriteRune(')')
+ }
+ fmt.Fprintf(&sb, ":%v", s.Count)
+ return sb.String()
+}
+
+type HistogramBuckets []*HistogramBucket
+
+func (s HistogramBuckets) Equal(o HistogramBuckets) bool {
+ if len(s) != len(o) {
+ return false
+ }
+
+ for i, bucket := range s {
+ if !bucket.Equal(o[i]) {
+ return false
+ }
+ }
+ return true
+}
+
+type SampleHistogram struct {
+ Count FloatString `json:"count"`
+ Sum FloatString `json:"sum"`
+ Buckets HistogramBuckets `json:"buckets"`
+}
+
+func (s SampleHistogram) String() string {
+ return fmt.Sprintf("Count: %f, Sum: %f, Buckets: %v", s.Count, s.Sum, s.Buckets)
+}
+
+func (s *SampleHistogram) Equal(o *SampleHistogram) bool {
+ return s == o || (s.Count == o.Count && s.Sum == o.Sum && s.Buckets.Equal(o.Buckets))
+}
+
+type SampleHistogramPair struct {
+ Timestamp Time
+ // Histogram should never be nil, it's only stored as pointer for efficiency.
+ Histogram *SampleHistogram
+}
+
+func (s SampleHistogramPair) MarshalJSON() ([]byte, error) {
+ if s.Histogram == nil {
+ return nil, errors.New("histogram is nil")
+ }
+ t, err := json.Marshal(s.Timestamp)
+ if err != nil {
+ return nil, err
+ }
+ v, err := json.Marshal(s.Histogram)
+ if err != nil {
+ return nil, err
+ }
+ return []byte(fmt.Sprintf("[%s,%s]", t, v)), nil
+}
+
+func (s *SampleHistogramPair) UnmarshalJSON(buf []byte) error {
+ tmp := []interface{}{&s.Timestamp, &s.Histogram}
+ wantLen := len(tmp)
+ if err := json.Unmarshal(buf, &tmp); err != nil {
+ return err
+ }
+ if gotLen := len(tmp); gotLen != wantLen {
+ return fmt.Errorf("wrong number of fields: %d != %d", gotLen, wantLen)
+ }
+ if s.Histogram == nil {
+ return errors.New("histogram is null")
+ }
+ return nil
+}
+
+func (s SampleHistogramPair) String() string {
+ return fmt.Sprintf("%s @[%s]", s.Histogram, s.Timestamp)
+}
+
+func (s *SampleHistogramPair) Equal(o *SampleHistogramPair) bool {
+ return s == o || (s.Histogram.Equal(o.Histogram) && s.Timestamp.Equal(o.Timestamp))
+}
diff --git a/vendor/github.com/prometheus/common/model/value_type.go b/vendor/github.com/prometheus/common/model/value_type.go
new file mode 100644
index 0000000..078910f
--- /dev/null
+++ b/vendor/github.com/prometheus/common/model/value_type.go
@@ -0,0 +1,83 @@
+// Copyright 2013 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
+
+import (
+ "encoding/json"
+ "fmt"
+)
+
+// Value is a generic interface for values resulting from a query evaluation.
+type Value interface {
+ Type() ValueType
+ String() string
+}
+
+func (Matrix) Type() ValueType { return ValMatrix }
+func (Vector) Type() ValueType { return ValVector }
+func (*Scalar) Type() ValueType { return ValScalar }
+func (*String) Type() ValueType { return ValString }
+
+type ValueType int
+
+const (
+ ValNone ValueType = iota
+ ValScalar
+ ValVector
+ ValMatrix
+ ValString
+)
+
+// MarshalJSON implements json.Marshaler.
+func (et ValueType) MarshalJSON() ([]byte, error) {
+ return json.Marshal(et.String())
+}
+
+func (et *ValueType) UnmarshalJSON(b []byte) error {
+ var s string
+ if err := json.Unmarshal(b, &s); err != nil {
+ return err
+ }
+ switch s {
+ case "<ValNone>":
+ *et = ValNone
+ case "scalar":
+ *et = ValScalar
+ case "vector":
+ *et = ValVector
+ case "matrix":
+ *et = ValMatrix
+ case "string":
+ *et = ValString
+ default:
+ return fmt.Errorf("unknown value type %q", s)
+ }
+ return nil
+}
+
+func (et ValueType) String() string {
+ switch et {
+ case ValNone:
+ return "<ValNone>"
+ case ValScalar:
+ return "scalar"
+ case ValVector:
+ return "vector"
+ case ValMatrix:
+ return "matrix"
+ case ValString:
+ return "string"
+ }
+ panic("ValueType.String: unhandled value type")
+}