go mod vendor
+ move k8s.io/apimachinery fork from go.work to go.mod (and include it in vendor)
This commit is contained in:
		
							
								
								
									
										429
									
								
								vendor/github.com/prometheus/common/expfmt/decode.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										429
									
								
								vendor/github.com/prometheus/common/expfmt/decode.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,429 @@ | ||||
| // Copyright 2015 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 expfmt | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"math" | ||||
| 	"mime" | ||||
| 	"net/http" | ||||
|  | ||||
| 	dto "github.com/prometheus/client_model/go" | ||||
|  | ||||
| 	"github.com/matttproud/golang_protobuf_extensions/pbutil" | ||||
| 	"github.com/prometheus/common/model" | ||||
| ) | ||||
|  | ||||
| // Decoder types decode an input stream into metric families. | ||||
| type Decoder interface { | ||||
| 	Decode(*dto.MetricFamily) error | ||||
| } | ||||
|  | ||||
| // DecodeOptions contains options used by the Decoder and in sample extraction. | ||||
| type DecodeOptions struct { | ||||
| 	// Timestamp is added to each value from the stream that has no explicit timestamp set. | ||||
| 	Timestamp model.Time | ||||
| } | ||||
|  | ||||
| // ResponseFormat extracts the correct format from a HTTP response header. | ||||
| // If no matching format can be found FormatUnknown is returned. | ||||
| func ResponseFormat(h http.Header) Format { | ||||
| 	ct := h.Get(hdrContentType) | ||||
|  | ||||
| 	mediatype, params, err := mime.ParseMediaType(ct) | ||||
| 	if err != nil { | ||||
| 		return FmtUnknown | ||||
| 	} | ||||
|  | ||||
| 	const textType = "text/plain" | ||||
|  | ||||
| 	switch mediatype { | ||||
| 	case ProtoType: | ||||
| 		if p, ok := params["proto"]; ok && p != ProtoProtocol { | ||||
| 			return FmtUnknown | ||||
| 		} | ||||
| 		if e, ok := params["encoding"]; ok && e != "delimited" { | ||||
| 			return FmtUnknown | ||||
| 		} | ||||
| 		return FmtProtoDelim | ||||
|  | ||||
| 	case textType: | ||||
| 		if v, ok := params["version"]; ok && v != TextVersion { | ||||
| 			return FmtUnknown | ||||
| 		} | ||||
| 		return FmtText | ||||
| 	} | ||||
|  | ||||
| 	return FmtUnknown | ||||
| } | ||||
|  | ||||
| // NewDecoder returns a new decoder based on the given input format. | ||||
| // If the input format does not imply otherwise, a text format decoder is returned. | ||||
| func NewDecoder(r io.Reader, format Format) Decoder { | ||||
| 	switch format { | ||||
| 	case FmtProtoDelim: | ||||
| 		return &protoDecoder{r: r} | ||||
| 	} | ||||
| 	return &textDecoder{r: r} | ||||
| } | ||||
|  | ||||
| // protoDecoder implements the Decoder interface for protocol buffers. | ||||
| type protoDecoder struct { | ||||
| 	r io.Reader | ||||
| } | ||||
|  | ||||
| // Decode implements the Decoder interface. | ||||
| func (d *protoDecoder) Decode(v *dto.MetricFamily) error { | ||||
| 	_, err := pbutil.ReadDelimited(d.r, v) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if !model.IsValidMetricName(model.LabelValue(v.GetName())) { | ||||
| 		return fmt.Errorf("invalid metric name %q", v.GetName()) | ||||
| 	} | ||||
| 	for _, m := range v.GetMetric() { | ||||
| 		if m == nil { | ||||
| 			continue | ||||
| 		} | ||||
| 		for _, l := range m.GetLabel() { | ||||
| 			if l == nil { | ||||
| 				continue | ||||
| 			} | ||||
| 			if !model.LabelValue(l.GetValue()).IsValid() { | ||||
| 				return fmt.Errorf("invalid label value %q", l.GetValue()) | ||||
| 			} | ||||
| 			if !model.LabelName(l.GetName()).IsValid() { | ||||
| 				return fmt.Errorf("invalid label name %q", l.GetName()) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // textDecoder implements the Decoder interface for the text protocol. | ||||
| type textDecoder struct { | ||||
| 	r    io.Reader | ||||
| 	p    TextParser | ||||
| 	fams []*dto.MetricFamily | ||||
| } | ||||
|  | ||||
| // 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) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	*v = *d.fams[0] | ||||
| 	d.fams = d.fams[1:] | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // SampleDecoder wraps a Decoder to extract samples from the metric families | ||||
| // decoded by the wrapped Decoder. | ||||
| type SampleDecoder struct { | ||||
| 	Dec  Decoder | ||||
| 	Opts *DecodeOptions | ||||
|  | ||||
| 	f dto.MetricFamily | ||||
| } | ||||
|  | ||||
| // Decode calls the Decode method of the wrapped Decoder and then extracts the | ||||
| // samples from the decoded MetricFamily into the provided model.Vector. | ||||
| func (sd *SampleDecoder) Decode(s *model.Vector) error { | ||||
| 	err := sd.Dec.Decode(&sd.f) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	*s, err = extractSamples(&sd.f, sd.Opts) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // ExtractSamples builds a slice of samples from the provided metric | ||||
| // families. If an error occurs during sample extraction, it continues to | ||||
| // extract from the remaining metric families. The returned error is the last | ||||
| // error that has occurred. | ||||
| func ExtractSamples(o *DecodeOptions, fams ...*dto.MetricFamily) (model.Vector, error) { | ||||
| 	var ( | ||||
| 		all     model.Vector | ||||
| 		lastErr error | ||||
| 	) | ||||
| 	for _, f := range fams { | ||||
| 		some, err := extractSamples(f, o) | ||||
| 		if err != nil { | ||||
| 			lastErr = err | ||||
| 			continue | ||||
| 		} | ||||
| 		all = append(all, some...) | ||||
| 	} | ||||
| 	return all, lastErr | ||||
| } | ||||
|  | ||||
| func extractSamples(f *dto.MetricFamily, o *DecodeOptions) (model.Vector, error) { | ||||
| 	switch f.GetType() { | ||||
| 	case dto.MetricType_COUNTER: | ||||
| 		return extractCounter(o, f), nil | ||||
| 	case dto.MetricType_GAUGE: | ||||
| 		return extractGauge(o, f), nil | ||||
| 	case dto.MetricType_SUMMARY: | ||||
| 		return extractSummary(o, f), nil | ||||
| 	case dto.MetricType_UNTYPED: | ||||
| 		return extractUntyped(o, f), nil | ||||
| 	case dto.MetricType_HISTOGRAM: | ||||
| 		return extractHistogram(o, f), nil | ||||
| 	} | ||||
| 	return nil, fmt.Errorf("expfmt.extractSamples: unknown metric family type %v", f.GetType()) | ||||
| } | ||||
|  | ||||
| func extractCounter(o *DecodeOptions, f *dto.MetricFamily) model.Vector { | ||||
| 	samples := make(model.Vector, 0, len(f.Metric)) | ||||
|  | ||||
| 	for _, m := range f.Metric { | ||||
| 		if m.Counter == nil { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		lset := make(model.LabelSet, len(m.Label)+1) | ||||
| 		for _, p := range m.Label { | ||||
| 			lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue()) | ||||
| 		} | ||||
| 		lset[model.MetricNameLabel] = model.LabelValue(f.GetName()) | ||||
|  | ||||
| 		smpl := &model.Sample{ | ||||
| 			Metric: model.Metric(lset), | ||||
| 			Value:  model.SampleValue(m.Counter.GetValue()), | ||||
| 		} | ||||
|  | ||||
| 		if m.TimestampMs != nil { | ||||
| 			smpl.Timestamp = model.TimeFromUnixNano(*m.TimestampMs * 1000000) | ||||
| 		} else { | ||||
| 			smpl.Timestamp = o.Timestamp | ||||
| 		} | ||||
|  | ||||
| 		samples = append(samples, smpl) | ||||
| 	} | ||||
|  | ||||
| 	return samples | ||||
| } | ||||
|  | ||||
| func extractGauge(o *DecodeOptions, f *dto.MetricFamily) model.Vector { | ||||
| 	samples := make(model.Vector, 0, len(f.Metric)) | ||||
|  | ||||
| 	for _, m := range f.Metric { | ||||
| 		if m.Gauge == nil { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		lset := make(model.LabelSet, len(m.Label)+1) | ||||
| 		for _, p := range m.Label { | ||||
| 			lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue()) | ||||
| 		} | ||||
| 		lset[model.MetricNameLabel] = model.LabelValue(f.GetName()) | ||||
|  | ||||
| 		smpl := &model.Sample{ | ||||
| 			Metric: model.Metric(lset), | ||||
| 			Value:  model.SampleValue(m.Gauge.GetValue()), | ||||
| 		} | ||||
|  | ||||
| 		if m.TimestampMs != nil { | ||||
| 			smpl.Timestamp = model.TimeFromUnixNano(*m.TimestampMs * 1000000) | ||||
| 		} else { | ||||
| 			smpl.Timestamp = o.Timestamp | ||||
| 		} | ||||
|  | ||||
| 		samples = append(samples, smpl) | ||||
| 	} | ||||
|  | ||||
| 	return samples | ||||
| } | ||||
|  | ||||
| func extractUntyped(o *DecodeOptions, f *dto.MetricFamily) model.Vector { | ||||
| 	samples := make(model.Vector, 0, len(f.Metric)) | ||||
|  | ||||
| 	for _, m := range f.Metric { | ||||
| 		if m.Untyped == nil { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		lset := make(model.LabelSet, len(m.Label)+1) | ||||
| 		for _, p := range m.Label { | ||||
| 			lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue()) | ||||
| 		} | ||||
| 		lset[model.MetricNameLabel] = model.LabelValue(f.GetName()) | ||||
|  | ||||
| 		smpl := &model.Sample{ | ||||
| 			Metric: model.Metric(lset), | ||||
| 			Value:  model.SampleValue(m.Untyped.GetValue()), | ||||
| 		} | ||||
|  | ||||
| 		if m.TimestampMs != nil { | ||||
| 			smpl.Timestamp = model.TimeFromUnixNano(*m.TimestampMs * 1000000) | ||||
| 		} else { | ||||
| 			smpl.Timestamp = o.Timestamp | ||||
| 		} | ||||
|  | ||||
| 		samples = append(samples, smpl) | ||||
| 	} | ||||
|  | ||||
| 	return samples | ||||
| } | ||||
|  | ||||
| func extractSummary(o *DecodeOptions, f *dto.MetricFamily) model.Vector { | ||||
| 	samples := make(model.Vector, 0, len(f.Metric)) | ||||
|  | ||||
| 	for _, m := range f.Metric { | ||||
| 		if m.Summary == nil { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		timestamp := o.Timestamp | ||||
| 		if m.TimestampMs != nil { | ||||
| 			timestamp = model.TimeFromUnixNano(*m.TimestampMs * 1000000) | ||||
| 		} | ||||
|  | ||||
| 		for _, q := range m.Summary.Quantile { | ||||
| 			lset := make(model.LabelSet, len(m.Label)+2) | ||||
| 			for _, p := range m.Label { | ||||
| 				lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue()) | ||||
| 			} | ||||
| 			// BUG(matt): Update other names to "quantile". | ||||
| 			lset[model.LabelName(model.QuantileLabel)] = model.LabelValue(fmt.Sprint(q.GetQuantile())) | ||||
| 			lset[model.MetricNameLabel] = model.LabelValue(f.GetName()) | ||||
|  | ||||
| 			samples = append(samples, &model.Sample{ | ||||
| 				Metric:    model.Metric(lset), | ||||
| 				Value:     model.SampleValue(q.GetValue()), | ||||
| 				Timestamp: timestamp, | ||||
| 			}) | ||||
| 		} | ||||
|  | ||||
| 		lset := make(model.LabelSet, len(m.Label)+1) | ||||
| 		for _, p := range m.Label { | ||||
| 			lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue()) | ||||
| 		} | ||||
| 		lset[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_sum") | ||||
|  | ||||
| 		samples = append(samples, &model.Sample{ | ||||
| 			Metric:    model.Metric(lset), | ||||
| 			Value:     model.SampleValue(m.Summary.GetSampleSum()), | ||||
| 			Timestamp: timestamp, | ||||
| 		}) | ||||
|  | ||||
| 		lset = make(model.LabelSet, len(m.Label)+1) | ||||
| 		for _, p := range m.Label { | ||||
| 			lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue()) | ||||
| 		} | ||||
| 		lset[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_count") | ||||
|  | ||||
| 		samples = append(samples, &model.Sample{ | ||||
| 			Metric:    model.Metric(lset), | ||||
| 			Value:     model.SampleValue(m.Summary.GetSampleCount()), | ||||
| 			Timestamp: timestamp, | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	return samples | ||||
| } | ||||
|  | ||||
| func extractHistogram(o *DecodeOptions, f *dto.MetricFamily) model.Vector { | ||||
| 	samples := make(model.Vector, 0, len(f.Metric)) | ||||
|  | ||||
| 	for _, m := range f.Metric { | ||||
| 		if m.Histogram == nil { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		timestamp := o.Timestamp | ||||
| 		if m.TimestampMs != nil { | ||||
| 			timestamp = model.TimeFromUnixNano(*m.TimestampMs * 1000000) | ||||
| 		} | ||||
|  | ||||
| 		infSeen := false | ||||
|  | ||||
| 		for _, q := range m.Histogram.Bucket { | ||||
| 			lset := make(model.LabelSet, len(m.Label)+2) | ||||
| 			for _, p := range m.Label { | ||||
| 				lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue()) | ||||
| 			} | ||||
| 			lset[model.LabelName(model.BucketLabel)] = model.LabelValue(fmt.Sprint(q.GetUpperBound())) | ||||
| 			lset[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_bucket") | ||||
|  | ||||
| 			if math.IsInf(q.GetUpperBound(), +1) { | ||||
| 				infSeen = true | ||||
| 			} | ||||
|  | ||||
| 			samples = append(samples, &model.Sample{ | ||||
| 				Metric:    model.Metric(lset), | ||||
| 				Value:     model.SampleValue(q.GetCumulativeCount()), | ||||
| 				Timestamp: timestamp, | ||||
| 			}) | ||||
| 		} | ||||
|  | ||||
| 		lset := make(model.LabelSet, len(m.Label)+1) | ||||
| 		for _, p := range m.Label { | ||||
| 			lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue()) | ||||
| 		} | ||||
| 		lset[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_sum") | ||||
|  | ||||
| 		samples = append(samples, &model.Sample{ | ||||
| 			Metric:    model.Metric(lset), | ||||
| 			Value:     model.SampleValue(m.Histogram.GetSampleSum()), | ||||
| 			Timestamp: timestamp, | ||||
| 		}) | ||||
|  | ||||
| 		lset = make(model.LabelSet, len(m.Label)+1) | ||||
| 		for _, p := range m.Label { | ||||
| 			lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue()) | ||||
| 		} | ||||
| 		lset[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_count") | ||||
|  | ||||
| 		count := &model.Sample{ | ||||
| 			Metric:    model.Metric(lset), | ||||
| 			Value:     model.SampleValue(m.Histogram.GetSampleCount()), | ||||
| 			Timestamp: timestamp, | ||||
| 		} | ||||
| 		samples = append(samples, count) | ||||
|  | ||||
| 		if !infSeen { | ||||
| 			// Append an infinity bucket sample. | ||||
| 			lset := make(model.LabelSet, len(m.Label)+2) | ||||
| 			for _, p := range m.Label { | ||||
| 				lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue()) | ||||
| 			} | ||||
| 			lset[model.LabelName(model.BucketLabel)] = model.LabelValue("+Inf") | ||||
| 			lset[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_bucket") | ||||
|  | ||||
| 			samples = append(samples, &model.Sample{ | ||||
| 				Metric:    model.Metric(lset), | ||||
| 				Value:     count.Value, | ||||
| 				Timestamp: timestamp, | ||||
| 			}) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return samples | ||||
| } | ||||
							
								
								
									
										162
									
								
								vendor/github.com/prometheus/common/expfmt/encode.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								vendor/github.com/prometheus/common/expfmt/encode.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,162 @@ | ||||
| // Copyright 2015 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 expfmt | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"net/http" | ||||
|  | ||||
| 	"github.com/golang/protobuf/proto" //nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility. | ||||
| 	"github.com/matttproud/golang_protobuf_extensions/pbutil" | ||||
| 	"github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg" | ||||
|  | ||||
| 	dto "github.com/prometheus/client_model/go" | ||||
| ) | ||||
|  | ||||
| // Encoder types encode metric families into an underlying wire protocol. | ||||
| type Encoder interface { | ||||
| 	Encode(*dto.MetricFamily) error | ||||
| } | ||||
|  | ||||
| // Closer is implemented by Encoders that need to be closed to finalize | ||||
| // encoding. (For example, OpenMetrics needs a final `# EOF` line.) | ||||
| // | ||||
| // Note that all Encoder implementations returned from this package implement | ||||
| // Closer, too, even if the Close call is a no-op. This happens in preparation | ||||
| // for adding a Close method to the Encoder interface directly in a (mildly | ||||
| // breaking) release in the future. | ||||
| type Closer interface { | ||||
| 	Close() error | ||||
| } | ||||
|  | ||||
| type encoderCloser struct { | ||||
| 	encode func(*dto.MetricFamily) error | ||||
| 	close  func() error | ||||
| } | ||||
|  | ||||
| func (ec encoderCloser) Encode(v *dto.MetricFamily) error { | ||||
| 	return ec.encode(v) | ||||
| } | ||||
|  | ||||
| func (ec encoderCloser) Close() error { | ||||
| 	return ec.close() | ||||
| } | ||||
|  | ||||
| // Negotiate returns the Content-Type based on the given Accept header. If no | ||||
| // 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. | ||||
| func Negotiate(h http.Header) Format { | ||||
| 	for _, ac := range goautoneg.ParseAccept(h.Get(hdrAccept)) { | ||||
| 		ver := ac.Params["version"] | ||||
| 		if ac.Type+"/"+ac.SubType == ProtoType && ac.Params["proto"] == ProtoProtocol { | ||||
| 			switch ac.Params["encoding"] { | ||||
| 			case "delimited": | ||||
| 				return FmtProtoDelim | ||||
| 			case "text": | ||||
| 				return FmtProtoText | ||||
| 			case "compact-text": | ||||
| 				return FmtProtoCompact | ||||
| 			} | ||||
| 		} | ||||
| 		if ac.Type == "text" && ac.SubType == "plain" && (ver == TextVersion || ver == "") { | ||||
| 			return FmtText | ||||
| 		} | ||||
| 	} | ||||
| 	return FmtText | ||||
| } | ||||
|  | ||||
| // NegotiateIncludingOpenMetrics works like Negotiate but includes | ||||
| // FmtOpenMetrics as an option for the result. Note that this function is | ||||
| // 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 { | ||||
| 	for _, ac := range goautoneg.ParseAccept(h.Get(hdrAccept)) { | ||||
| 		ver := ac.Params["version"] | ||||
| 		if ac.Type+"/"+ac.SubType == ProtoType && ac.Params["proto"] == ProtoProtocol { | ||||
| 			switch ac.Params["encoding"] { | ||||
| 			case "delimited": | ||||
| 				return FmtProtoDelim | ||||
| 			case "text": | ||||
| 				return FmtProtoText | ||||
| 			case "compact-text": | ||||
| 				return FmtProtoCompact | ||||
| 			} | ||||
| 		} | ||||
| 		if ac.Type == "text" && ac.SubType == "plain" && (ver == TextVersion || ver == "") { | ||||
| 			return FmtText | ||||
| 		} | ||||
| 		if ac.Type+"/"+ac.SubType == OpenMetricsType && (ver == OpenMetricsVersion || ver == "") { | ||||
| 			return FmtOpenMetrics | ||||
| 		} | ||||
| 	} | ||||
| 	return FmtText | ||||
| } | ||||
|  | ||||
| // NewEncoder returns a new encoder based on content type negotiation. All | ||||
| // Encoder implementations returned by NewEncoder also implement Closer, and | ||||
| // callers should always call the Close method. It is currently only required | ||||
| // 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: | ||||
| 		return encoderCloser{ | ||||
| 			encode: func(v *dto.MetricFamily) error { | ||||
| 				_, err := pbutil.WriteDelimited(w, v) | ||||
| 				return err | ||||
| 			}, | ||||
| 			close: func() error { return nil }, | ||||
| 		} | ||||
| 	case FmtProtoCompact: | ||||
| 		return encoderCloser{ | ||||
| 			encode: func(v *dto.MetricFamily) error { | ||||
| 				_, err := fmt.Fprintln(w, v.String()) | ||||
| 				return err | ||||
| 			}, | ||||
| 			close: func() error { return nil }, | ||||
| 		} | ||||
| 	case FmtProtoText: | ||||
| 		return encoderCloser{ | ||||
| 			encode: func(v *dto.MetricFamily) error { | ||||
| 				_, err := fmt.Fprintln(w, proto.MarshalTextString(v)) | ||||
| 				return err | ||||
| 			}, | ||||
| 			close: func() error { return nil }, | ||||
| 		} | ||||
| 	case FmtText: | ||||
| 		return encoderCloser{ | ||||
| 			encode: func(v *dto.MetricFamily) error { | ||||
| 				_, err := MetricFamilyToText(w, v) | ||||
| 				return err | ||||
| 			}, | ||||
| 			close: func() error { return nil }, | ||||
| 		} | ||||
| 	case FmtOpenMetrics: | ||||
| 		return encoderCloser{ | ||||
| 			encode: func(v *dto.MetricFamily) error { | ||||
| 				_, err := MetricFamilyToOpenMetrics(w, v) | ||||
| 				return err | ||||
| 			}, | ||||
| 			close: func() error { | ||||
| 				_, err := FinalizeOpenMetrics(w) | ||||
| 				return err | ||||
| 			}, | ||||
| 		} | ||||
| 	} | ||||
| 	panic(fmt.Errorf("expfmt.NewEncoder: unknown format %q", format)) | ||||
| } | ||||
							
								
								
									
										41
									
								
								vendor/github.com/prometheus/common/expfmt/expfmt.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								vendor/github.com/prometheus/common/expfmt/expfmt.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | ||||
| // Copyright 2015 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 expfmt contains tools for reading and writing Prometheus metrics. | ||||
| package expfmt | ||||
|  | ||||
| // 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. | ||||
| 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" | ||||
|  | ||||
| 	// The Content-Type values for the different wire protocols. | ||||
| 	FmtUnknown      Format = `<unknown>` | ||||
| 	FmtText         Format = `text/plain; version=` + TextVersion + `; charset=utf-8` | ||||
| 	FmtProtoDelim   Format = ProtoFmt + ` encoding=delimited` | ||||
| 	FmtProtoText    Format = ProtoFmt + ` encoding=text` | ||||
| 	FmtProtoCompact Format = ProtoFmt + ` encoding=compact-text` | ||||
| 	FmtOpenMetrics  Format = OpenMetricsType + `; version=` + OpenMetricsVersion + `; charset=utf-8` | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	hdrContentType = "Content-Type" | ||||
| 	hdrAccept      = "Accept" | ||||
| ) | ||||
							
								
								
									
										37
									
								
								vendor/github.com/prometheus/common/expfmt/fuzz.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								vendor/github.com/prometheus/common/expfmt/fuzz.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| // Copyright 2014 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. | ||||
|  | ||||
| // Build only when actually fuzzing | ||||
| //go:build gofuzz | ||||
| // +build gofuzz | ||||
|  | ||||
| package expfmt | ||||
|  | ||||
| import "bytes" | ||||
|  | ||||
| // 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 | ||||
| // | ||||
| // Further input samples should go in the folder fuzz/corpus. | ||||
| func Fuzz(in []byte) int { | ||||
| 	parser := TextParser{} | ||||
| 	_, err := parser.TextToMetricFamilies(bytes.NewReader(in)) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return 0 | ||||
| 	} | ||||
|  | ||||
| 	return 1 | ||||
| } | ||||
							
								
								
									
										527
									
								
								vendor/github.com/prometheus/common/expfmt/openmetrics_create.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										527
									
								
								vendor/github.com/prometheus/common/expfmt/openmetrics_create.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,527 @@ | ||||
| // Copyright 2020 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 expfmt | ||||
|  | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"math" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/prometheus/common/model" | ||||
|  | ||||
| 	dto "github.com/prometheus/client_model/go" | ||||
| ) | ||||
|  | ||||
| // 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 | ||||
| // the same order as the input, no further sorting is performed. Furthermore, | ||||
| // this function assumes the input is already sanitized and does not perform any | ||||
| // sanity checks. If the input contains duplicate metrics or invalid metric or | ||||
| // label names, the conversion will result in invalid text format output. | ||||
| // | ||||
| // This function fulfills the type 'expfmt.encoder'. | ||||
| // | ||||
| // Note that OpenMetrics requires a final `# EOF` line. Since this function acts | ||||
| // on individual metric families, it is the responsibility of the caller to | ||||
| // append this line to 'out' once all metric families have been written. | ||||
| // Conveniently, this can be done by calling FinalizeOpenMetrics. | ||||
| // | ||||
| // The output should be fully OpenMetrics compliant. However, there are a few | ||||
| // 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. | ||||
| // | ||||
| // - No support for the following (optional) features: `# UNIT` line, `_created` | ||||
| //   line, info type, stateset type, gaugehistogram type. | ||||
| // | ||||
| // - 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) (written int, err error) { | ||||
| 	name := in.GetName() | ||||
| 	if name == "" { | ||||
| 		return 0, fmt.Errorf("MetricFamily has no name: %s", in) | ||||
| 	} | ||||
|  | ||||
| 	// Try the interface upgrade. If it doesn't work, we'll use a | ||||
| 	// bufio.Writer from the sync.Pool. | ||||
| 	w, ok := out.(enhancedWriter) | ||||
| 	if !ok { | ||||
| 		b := bufPool.Get().(*bufio.Writer) | ||||
| 		b.Reset(out) | ||||
| 		w = b | ||||
| 		defer func() { | ||||
| 			bErr := b.Flush() | ||||
| 			if err == nil { | ||||
| 				err = bErr | ||||
| 			} | ||||
| 			bufPool.Put(b) | ||||
| 		}() | ||||
| 	} | ||||
|  | ||||
| 	var ( | ||||
| 		n          int | ||||
| 		metricType = in.GetType() | ||||
| 		shortName  = name | ||||
| 	) | ||||
| 	if metricType == dto.MetricType_COUNTER && strings.HasSuffix(shortName, "_total") { | ||||
| 		shortName = name[:len(name)-6] | ||||
| 	} | ||||
|  | ||||
| 	// Comments, first HELP, then TYPE. | ||||
| 	if in.Help != nil { | ||||
| 		n, err = w.WriteString("# HELP ") | ||||
| 		written += n | ||||
| 		if err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 		n, err = w.WriteString(shortName) | ||||
| 		written += n | ||||
| 		if err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 		err = w.WriteByte(' ') | ||||
| 		written++ | ||||
| 		if err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 		n, err = writeEscapedString(w, *in.Help, true) | ||||
| 		written += n | ||||
| 		if err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 		err = w.WriteByte('\n') | ||||
| 		written++ | ||||
| 		if err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	n, err = w.WriteString("# TYPE ") | ||||
| 	written += n | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	n, err = w.WriteString(shortName) | ||||
| 	written += n | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	switch metricType { | ||||
| 	case dto.MetricType_COUNTER: | ||||
| 		if strings.HasSuffix(name, "_total") { | ||||
| 			n, err = w.WriteString(" counter\n") | ||||
| 		} else { | ||||
| 			n, err = w.WriteString(" unknown\n") | ||||
| 		} | ||||
| 	case dto.MetricType_GAUGE: | ||||
| 		n, err = w.WriteString(" gauge\n") | ||||
| 	case dto.MetricType_SUMMARY: | ||||
| 		n, err = w.WriteString(" summary\n") | ||||
| 	case dto.MetricType_UNTYPED: | ||||
| 		n, err = w.WriteString(" unknown\n") | ||||
| 	case dto.MetricType_HISTOGRAM: | ||||
| 		n, err = w.WriteString(" histogram\n") | ||||
| 	default: | ||||
| 		return written, fmt.Errorf("unknown metric type %s", metricType.String()) | ||||
| 	} | ||||
| 	written += n | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Finally the samples, one line for each. | ||||
| 	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, | ||||
| 				) | ||||
| 			} | ||||
| 			// 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, | ||||
| 				metric.Counter.GetValue(), 0, false, | ||||
| 				metric.Counter.Exemplar, | ||||
| 			) | ||||
| 		case dto.MetricType_GAUGE: | ||||
| 			if metric.Gauge == nil { | ||||
| 				return written, fmt.Errorf( | ||||
| 					"expected gauge in metric %s %s", name, metric, | ||||
| 				) | ||||
| 			} | ||||
| 			n, err = writeOpenMetricsSample( | ||||
| 				w, name, "", 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, | ||||
| 				) | ||||
| 			} | ||||
| 			n, err = writeOpenMetricsSample( | ||||
| 				w, name, "", 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, | ||||
| 				) | ||||
| 			} | ||||
| 			for _, q := range metric.Summary.Quantile { | ||||
| 				n, err = writeOpenMetricsSample( | ||||
| 					w, name, "", metric, | ||||
| 					model.QuantileLabel, q.GetQuantile(), | ||||
| 					q.GetValue(), 0, false, | ||||
| 					nil, | ||||
| 				) | ||||
| 				written += n | ||||
| 				if err != nil { | ||||
| 					return | ||||
| 				} | ||||
| 			} | ||||
| 			n, err = writeOpenMetricsSample( | ||||
| 				w, name, "_sum", metric, "", 0, | ||||
| 				metric.Summary.GetSampleSum(), 0, false, | ||||
| 				nil, | ||||
| 			) | ||||
| 			written += n | ||||
| 			if err != nil { | ||||
| 				return | ||||
| 			} | ||||
| 			n, err = writeOpenMetricsSample( | ||||
| 				w, name, "_count", metric, "", 0, | ||||
| 				0, metric.Summary.GetSampleCount(), true, | ||||
| 				nil, | ||||
| 			) | ||||
| 		case dto.MetricType_HISTOGRAM: | ||||
| 			if metric.Histogram == nil { | ||||
| 				return written, fmt.Errorf( | ||||
| 					"expected histogram in metric %s %s", name, metric, | ||||
| 				) | ||||
| 			} | ||||
| 			infSeen := false | ||||
| 			for _, b := range metric.Histogram.Bucket { | ||||
| 				n, err = writeOpenMetricsSample( | ||||
| 					w, name, "_bucket", metric, | ||||
| 					model.BucketLabel, b.GetUpperBound(), | ||||
| 					0, b.GetCumulativeCount(), true, | ||||
| 					b.Exemplar, | ||||
| 				) | ||||
| 				written += n | ||||
| 				if err != nil { | ||||
| 					return | ||||
| 				} | ||||
| 				if math.IsInf(b.GetUpperBound(), +1) { | ||||
| 					infSeen = true | ||||
| 				} | ||||
| 			} | ||||
| 			if !infSeen { | ||||
| 				n, err = writeOpenMetricsSample( | ||||
| 					w, name, "_bucket", metric, | ||||
| 					model.BucketLabel, math.Inf(+1), | ||||
| 					0, metric.Histogram.GetSampleCount(), true, | ||||
| 					nil, | ||||
| 				) | ||||
| 				written += n | ||||
| 				if err != nil { | ||||
| 					return | ||||
| 				} | ||||
| 			} | ||||
| 			n, err = writeOpenMetricsSample( | ||||
| 				w, name, "_sum", metric, "", 0, | ||||
| 				metric.Histogram.GetSampleSum(), 0, false, | ||||
| 				nil, | ||||
| 			) | ||||
| 			written += n | ||||
| 			if err != nil { | ||||
| 				return | ||||
| 			} | ||||
| 			n, err = writeOpenMetricsSample( | ||||
| 				w, name, "_count", metric, "", 0, | ||||
| 				0, metric.Histogram.GetSampleCount(), true, | ||||
| 				nil, | ||||
| 			) | ||||
| 		default: | ||||
| 			return written, fmt.Errorf( | ||||
| 				"unexpected type in metric %s %s", name, metric, | ||||
| 			) | ||||
| 		} | ||||
| 		written += n | ||||
| 		if err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // FinalizeOpenMetrics writes the final `# EOF\n` line required by OpenMetrics. | ||||
| func FinalizeOpenMetrics(w io.Writer) (written int, err error) { | ||||
| 	return w.Write([]byte("# EOF\n")) | ||||
| } | ||||
|  | ||||
| // writeOpenMetricsSample writes a single sample in OpenMetrics text format to | ||||
| // w, given the metric name, the metric proto message itself, optionally an | ||||
| // additional label name with a float64 value (use empty string as label name if | ||||
| // not required), the value (optionally as float64 or uint64, determined by | ||||
| // useIntValue), and optionally an exemplar (use nil if not required). The | ||||
| // function returns the number of bytes written and any error encountered. | ||||
| func writeOpenMetricsSample( | ||||
| 	w enhancedWriter, | ||||
| 	name, suffix string, | ||||
| 	metric *dto.Metric, | ||||
| 	additionalLabelName string, additionalLabelValue float64, | ||||
| 	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 += n | ||||
| 	if err != nil { | ||||
| 		return written, err | ||||
| 	} | ||||
| 	err = w.WriteByte(' ') | ||||
| 	written++ | ||||
| 	if err != nil { | ||||
| 		return written, err | ||||
| 	} | ||||
| 	if useIntValue { | ||||
| 		n, err = writeUint(w, intValue) | ||||
| 	} else { | ||||
| 		n, err = writeOpenMetricsFloat(w, floatValue) | ||||
| 	} | ||||
| 	written += n | ||||
| 	if err != nil { | ||||
| 		return written, err | ||||
| 	} | ||||
| 	if metric.TimestampMs != nil { | ||||
| 		err = w.WriteByte(' ') | ||||
| 		written++ | ||||
| 		if err != nil { | ||||
| 			return written, err | ||||
| 		} | ||||
| 		// TODO(beorn7): Format this directly without converting to a float first. | ||||
| 		n, err = writeOpenMetricsFloat(w, float64(*metric.TimestampMs)/1000) | ||||
| 		written += n | ||||
| 		if err != nil { | ||||
| 			return written, err | ||||
| 		} | ||||
| 	} | ||||
| 	if exemplar != nil { | ||||
| 		n, err = writeExemplar(w, exemplar) | ||||
| 		written += n | ||||
| 		if err != nil { | ||||
| 			return written, err | ||||
| 		} | ||||
| 	} | ||||
| 	err = w.WriteByte('\n') | ||||
| 	written++ | ||||
| 	if err != nil { | ||||
| 		return written, err | ||||
| 	} | ||||
| 	return written, nil | ||||
| } | ||||
|  | ||||
| // writeOpenMetricsLabelPairs works like writeOpenMetrics but formats the float | ||||
| // in OpenMetrics style. | ||||
| func writeOpenMetricsLabelPairs( | ||||
| 	w enhancedWriter, | ||||
| 	in []*dto.LabelPair, | ||||
| 	additionalLabelName string, additionalLabelValue float64, | ||||
| ) (int, error) { | ||||
| 	if len(in) == 0 && additionalLabelName == "" { | ||||
| 		return 0, nil | ||||
| 	} | ||||
| 	var ( | ||||
| 		written   int | ||||
| 		separator byte = '{' | ||||
| 	) | ||||
| 	for _, lp := range in { | ||||
| 		err := w.WriteByte(separator) | ||||
| 		written++ | ||||
| 		if err != nil { | ||||
| 			return written, err | ||||
| 		} | ||||
| 		n, err := w.WriteString(lp.GetName()) | ||||
| 		written += n | ||||
| 		if err != nil { | ||||
| 			return written, err | ||||
| 		} | ||||
| 		n, err = w.WriteString(`="`) | ||||
| 		written += n | ||||
| 		if err != nil { | ||||
| 			return written, err | ||||
| 		} | ||||
| 		n, err = writeEscapedString(w, lp.GetValue(), true) | ||||
| 		written += n | ||||
| 		if err != nil { | ||||
| 			return written, err | ||||
| 		} | ||||
| 		err = w.WriteByte('"') | ||||
| 		written++ | ||||
| 		if err != nil { | ||||
| 			return written, err | ||||
| 		} | ||||
| 		separator = ',' | ||||
| 	} | ||||
| 	if additionalLabelName != "" { | ||||
| 		err := w.WriteByte(separator) | ||||
| 		written++ | ||||
| 		if err != nil { | ||||
| 			return written, err | ||||
| 		} | ||||
| 		n, err := w.WriteString(additionalLabelName) | ||||
| 		written += n | ||||
| 		if err != nil { | ||||
| 			return written, err | ||||
| 		} | ||||
| 		n, err = w.WriteString(`="`) | ||||
| 		written += n | ||||
| 		if err != nil { | ||||
| 			return written, err | ||||
| 		} | ||||
| 		n, err = writeOpenMetricsFloat(w, additionalLabelValue) | ||||
| 		written += n | ||||
| 		if err != nil { | ||||
| 			return written, err | ||||
| 		} | ||||
| 		err = w.WriteByte('"') | ||||
| 		written++ | ||||
| 		if err != nil { | ||||
| 			return written, err | ||||
| 		} | ||||
| 	} | ||||
| 	err := w.WriteByte('}') | ||||
| 	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) { | ||||
| 	written := 0 | ||||
| 	n, err := w.WriteString(" # ") | ||||
| 	written += n | ||||
| 	if err != nil { | ||||
| 		return written, err | ||||
| 	} | ||||
| 	n, err = writeOpenMetricsLabelPairs(w, e.Label, "", 0) | ||||
| 	written += n | ||||
| 	if err != nil { | ||||
| 		return written, err | ||||
| 	} | ||||
| 	err = w.WriteByte(' ') | ||||
| 	written++ | ||||
| 	if err != nil { | ||||
| 		return written, err | ||||
| 	} | ||||
| 	n, err = writeOpenMetricsFloat(w, e.GetValue()) | ||||
| 	written += n | ||||
| 	if err != nil { | ||||
| 		return written, err | ||||
| 	} | ||||
| 	if e.Timestamp != nil { | ||||
| 		err = w.WriteByte(' ') | ||||
| 		written++ | ||||
| 		if err != nil { | ||||
| 			return written, err | ||||
| 		} | ||||
| 		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. | ||||
| 		n, err = writeOpenMetricsFloat(w, float64(ts.UnixNano())/1e9) | ||||
| 		written += n | ||||
| 		if err != nil { | ||||
| 			return written, err | ||||
| 		} | ||||
| 	} | ||||
| 	return written, nil | ||||
| } | ||||
|  | ||||
| // writeOpenMetricsFloat works like writeFloat but appends ".0" if the resulting | ||||
| // number would otherwise contain neither a "." nor an "e". | ||||
| func writeOpenMetricsFloat(w enhancedWriter, f float64) (int, error) { | ||||
| 	switch { | ||||
| 	case f == 1: | ||||
| 		return w.WriteString("1.0") | ||||
| 	case f == 0: | ||||
| 		return w.WriteString("0.0") | ||||
| 	case f == -1: | ||||
| 		return w.WriteString("-1.0") | ||||
| 	case math.IsNaN(f): | ||||
| 		return w.WriteString("NaN") | ||||
| 	case math.IsInf(f, +1): | ||||
| 		return w.WriteString("+Inf") | ||||
| 	case math.IsInf(f, -1): | ||||
| 		return w.WriteString("-Inf") | ||||
| 	default: | ||||
| 		bp := numBufPool.Get().(*[]byte) | ||||
| 		*bp = strconv.AppendFloat((*bp)[:0], f, 'g', -1, 64) | ||||
| 		if !bytes.ContainsAny(*bp, "e.") { | ||||
| 			*bp = append(*bp, '.', '0') | ||||
| 		} | ||||
| 		written, err := w.Write(*bp) | ||||
| 		numBufPool.Put(bp) | ||||
| 		return written, err | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // writeUint is like writeInt just for uint64. | ||||
| func writeUint(w enhancedWriter, u uint64) (int, error) { | ||||
| 	bp := numBufPool.Get().(*[]byte) | ||||
| 	*bp = strconv.AppendUint((*bp)[:0], u, 10) | ||||
| 	written, err := w.Write(*bp) | ||||
| 	numBufPool.Put(bp) | ||||
| 	return written, err | ||||
| } | ||||
							
								
								
									
										465
									
								
								vendor/github.com/prometheus/common/expfmt/text_create.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										465
									
								
								vendor/github.com/prometheus/common/expfmt/text_create.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,465 @@ | ||||
| // Copyright 2014 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 expfmt | ||||
|  | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"math" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
|  | ||||
| 	"github.com/prometheus/common/model" | ||||
|  | ||||
| 	dto "github.com/prometheus/client_model/go" | ||||
| ) | ||||
|  | ||||
| // enhancedWriter has all the enhanced write functions needed here. bufio.Writer | ||||
| // implements it. | ||||
| type enhancedWriter interface { | ||||
| 	io.Writer | ||||
| 	WriteRune(r rune) (n int, err error) | ||||
| 	WriteString(s string) (n int, err error) | ||||
| 	WriteByte(c byte) error | ||||
| } | ||||
|  | ||||
| const ( | ||||
| 	initialNumBufSize = 24 | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	bufPool = sync.Pool{ | ||||
| 		New: func() interface{} { | ||||
| 			return bufio.NewWriter(ioutil.Discard) | ||||
| 		}, | ||||
| 	} | ||||
| 	numBufPool = sync.Pool{ | ||||
| 		New: func() interface{} { | ||||
| 			b := make([]byte, 0, initialNumBufSize) | ||||
| 			return &b | ||||
| 		}, | ||||
| 	} | ||||
| ) | ||||
|  | ||||
| // MetricFamilyToText converts a MetricFamily proto message into text format and | ||||
| // writes the resulting lines to 'out'. It returns the number of bytes written | ||||
| // and any error encountered. The output will have the same order as the input, | ||||
| // no further sorting is performed. Furthermore, this function assumes the input | ||||
| // is already sanitized and does not perform any sanity checks. If the input | ||||
| // contains duplicate metrics or invalid metric or label names, the conversion | ||||
| // will result in invalid text format output. | ||||
| // | ||||
| // This method fulfills the type 'prometheus.encoder'. | ||||
| func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err error) { | ||||
| 	// Fail-fast checks. | ||||
| 	if len(in.Metric) == 0 { | ||||
| 		return 0, fmt.Errorf("MetricFamily has no metrics: %s", in) | ||||
| 	} | ||||
| 	name := in.GetName() | ||||
| 	if name == "" { | ||||
| 		return 0, fmt.Errorf("MetricFamily has no name: %s", in) | ||||
| 	} | ||||
|  | ||||
| 	// Try the interface upgrade. If it doesn't work, we'll use a | ||||
| 	// bufio.Writer from the sync.Pool. | ||||
| 	w, ok := out.(enhancedWriter) | ||||
| 	if !ok { | ||||
| 		b := bufPool.Get().(*bufio.Writer) | ||||
| 		b.Reset(out) | ||||
| 		w = b | ||||
| 		defer func() { | ||||
| 			bErr := b.Flush() | ||||
| 			if err == nil { | ||||
| 				err = bErr | ||||
| 			} | ||||
| 			bufPool.Put(b) | ||||
| 		}() | ||||
| 	} | ||||
|  | ||||
| 	var n int | ||||
|  | ||||
| 	// Comments, first HELP, then TYPE. | ||||
| 	if in.Help != nil { | ||||
| 		n, err = w.WriteString("# HELP ") | ||||
| 		written += n | ||||
| 		if err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 		n, err = w.WriteString(name) | ||||
| 		written += n | ||||
| 		if err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 		err = w.WriteByte(' ') | ||||
| 		written++ | ||||
| 		if err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 		n, err = writeEscapedString(w, *in.Help, false) | ||||
| 		written += n | ||||
| 		if err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 		err = w.WriteByte('\n') | ||||
| 		written++ | ||||
| 		if err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	n, err = w.WriteString("# TYPE ") | ||||
| 	written += n | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	n, err = w.WriteString(name) | ||||
| 	written += n | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	metricType := in.GetType() | ||||
| 	switch metricType { | ||||
| 	case dto.MetricType_COUNTER: | ||||
| 		n, err = w.WriteString(" counter\n") | ||||
| 	case dto.MetricType_GAUGE: | ||||
| 		n, err = w.WriteString(" gauge\n") | ||||
| 	case dto.MetricType_SUMMARY: | ||||
| 		n, err = w.WriteString(" summary\n") | ||||
| 	case dto.MetricType_UNTYPED: | ||||
| 		n, err = w.WriteString(" untyped\n") | ||||
| 	case dto.MetricType_HISTOGRAM: | ||||
| 		n, err = w.WriteString(" histogram\n") | ||||
| 	default: | ||||
| 		return written, fmt.Errorf("unknown metric type %s", metricType.String()) | ||||
| 	} | ||||
| 	written += n | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Finally the samples, one line for each. | ||||
| 	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, | ||||
| 				) | ||||
| 			} | ||||
| 			n, err = writeSample( | ||||
| 				w, name, "", metric, "", 0, | ||||
| 				metric.Counter.GetValue(), | ||||
| 			) | ||||
| 		case dto.MetricType_GAUGE: | ||||
| 			if metric.Gauge == nil { | ||||
| 				return written, fmt.Errorf( | ||||
| 					"expected gauge in metric %s %s", name, metric, | ||||
| 				) | ||||
| 			} | ||||
| 			n, err = writeSample( | ||||
| 				w, name, "", metric, "", 0, | ||||
| 				metric.Gauge.GetValue(), | ||||
| 			) | ||||
| 		case dto.MetricType_UNTYPED: | ||||
| 			if metric.Untyped == nil { | ||||
| 				return written, fmt.Errorf( | ||||
| 					"expected untyped in metric %s %s", name, metric, | ||||
| 				) | ||||
| 			} | ||||
| 			n, err = writeSample( | ||||
| 				w, name, "", metric, "", 0, | ||||
| 				metric.Untyped.GetValue(), | ||||
| 			) | ||||
| 		case dto.MetricType_SUMMARY: | ||||
| 			if metric.Summary == nil { | ||||
| 				return written, fmt.Errorf( | ||||
| 					"expected summary in metric %s %s", name, metric, | ||||
| 				) | ||||
| 			} | ||||
| 			for _, q := range metric.Summary.Quantile { | ||||
| 				n, err = writeSample( | ||||
| 					w, name, "", metric, | ||||
| 					model.QuantileLabel, q.GetQuantile(), | ||||
| 					q.GetValue(), | ||||
| 				) | ||||
| 				written += n | ||||
| 				if err != nil { | ||||
| 					return | ||||
| 				} | ||||
| 			} | ||||
| 			n, err = writeSample( | ||||
| 				w, name, "_sum", metric, "", 0, | ||||
| 				metric.Summary.GetSampleSum(), | ||||
| 			) | ||||
| 			written += n | ||||
| 			if err != nil { | ||||
| 				return | ||||
| 			} | ||||
| 			n, err = writeSample( | ||||
| 				w, name, "_count", metric, "", 0, | ||||
| 				float64(metric.Summary.GetSampleCount()), | ||||
| 			) | ||||
| 		case dto.MetricType_HISTOGRAM: | ||||
| 			if metric.Histogram == nil { | ||||
| 				return written, fmt.Errorf( | ||||
| 					"expected histogram in metric %s %s", name, metric, | ||||
| 				) | ||||
| 			} | ||||
| 			infSeen := false | ||||
| 			for _, b := range metric.Histogram.Bucket { | ||||
| 				n, err = writeSample( | ||||
| 					w, name, "_bucket", metric, | ||||
| 					model.BucketLabel, b.GetUpperBound(), | ||||
| 					float64(b.GetCumulativeCount()), | ||||
| 				) | ||||
| 				written += n | ||||
| 				if err != nil { | ||||
| 					return | ||||
| 				} | ||||
| 				if math.IsInf(b.GetUpperBound(), +1) { | ||||
| 					infSeen = true | ||||
| 				} | ||||
| 			} | ||||
| 			if !infSeen { | ||||
| 				n, err = writeSample( | ||||
| 					w, name, "_bucket", metric, | ||||
| 					model.BucketLabel, math.Inf(+1), | ||||
| 					float64(metric.Histogram.GetSampleCount()), | ||||
| 				) | ||||
| 				written += n | ||||
| 				if err != nil { | ||||
| 					return | ||||
| 				} | ||||
| 			} | ||||
| 			n, err = writeSample( | ||||
| 				w, name, "_sum", metric, "", 0, | ||||
| 				metric.Histogram.GetSampleSum(), | ||||
| 			) | ||||
| 			written += n | ||||
| 			if err != nil { | ||||
| 				return | ||||
| 			} | ||||
| 			n, err = writeSample( | ||||
| 				w, name, "_count", metric, "", 0, | ||||
| 				float64(metric.Histogram.GetSampleCount()), | ||||
| 			) | ||||
| 		default: | ||||
| 			return written, fmt.Errorf( | ||||
| 				"unexpected type in metric %s %s", name, metric, | ||||
| 			) | ||||
| 		} | ||||
| 		written += n | ||||
| 		if err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // writeSample writes a single sample in text format to w, given the metric | ||||
| // name, the metric proto message itself, optionally an additional label name | ||||
| // with a float64 value (use empty string as label name if not required), and | ||||
| // the value. The function returns the number of bytes written and any error | ||||
| // encountered. | ||||
| func writeSample( | ||||
| 	w enhancedWriter, | ||||
| 	name, suffix string, | ||||
| 	metric *dto.Metric, | ||||
| 	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 += n | ||||
| 	if err != nil { | ||||
| 		return written, err | ||||
| 	} | ||||
| 	err = w.WriteByte(' ') | ||||
| 	written++ | ||||
| 	if err != nil { | ||||
| 		return written, err | ||||
| 	} | ||||
| 	n, err = writeFloat(w, value) | ||||
| 	written += n | ||||
| 	if err != nil { | ||||
| 		return written, err | ||||
| 	} | ||||
| 	if metric.TimestampMs != nil { | ||||
| 		err = w.WriteByte(' ') | ||||
| 		written++ | ||||
| 		if err != nil { | ||||
| 			return written, err | ||||
| 		} | ||||
| 		n, err = writeInt(w, *metric.TimestampMs) | ||||
| 		written += n | ||||
| 		if err != nil { | ||||
| 			return written, err | ||||
| 		} | ||||
| 	} | ||||
| 	err = w.WriteByte('\n') | ||||
| 	written++ | ||||
| 	if err != nil { | ||||
| 		return written, err | ||||
| 	} | ||||
| 	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( | ||||
| 	w enhancedWriter, | ||||
| 	in []*dto.LabelPair, | ||||
| 	additionalLabelName string, additionalLabelValue float64, | ||||
| ) (int, error) { | ||||
| 	if len(in) == 0 && additionalLabelName == "" { | ||||
| 		return 0, nil | ||||
| 	} | ||||
| 	var ( | ||||
| 		written   int | ||||
| 		separator byte = '{' | ||||
| 	) | ||||
| 	for _, lp := range in { | ||||
| 		err := w.WriteByte(separator) | ||||
| 		written++ | ||||
| 		if err != nil { | ||||
| 			return written, err | ||||
| 		} | ||||
| 		n, err := w.WriteString(lp.GetName()) | ||||
| 		written += n | ||||
| 		if err != nil { | ||||
| 			return written, err | ||||
| 		} | ||||
| 		n, err = w.WriteString(`="`) | ||||
| 		written += n | ||||
| 		if err != nil { | ||||
| 			return written, err | ||||
| 		} | ||||
| 		n, err = writeEscapedString(w, lp.GetValue(), true) | ||||
| 		written += n | ||||
| 		if err != nil { | ||||
| 			return written, err | ||||
| 		} | ||||
| 		err = w.WriteByte('"') | ||||
| 		written++ | ||||
| 		if err != nil { | ||||
| 			return written, err | ||||
| 		} | ||||
| 		separator = ',' | ||||
| 	} | ||||
| 	if additionalLabelName != "" { | ||||
| 		err := w.WriteByte(separator) | ||||
| 		written++ | ||||
| 		if err != nil { | ||||
| 			return written, err | ||||
| 		} | ||||
| 		n, err := w.WriteString(additionalLabelName) | ||||
| 		written += n | ||||
| 		if err != nil { | ||||
| 			return written, err | ||||
| 		} | ||||
| 		n, err = w.WriteString(`="`) | ||||
| 		written += n | ||||
| 		if err != nil { | ||||
| 			return written, err | ||||
| 		} | ||||
| 		n, err = writeFloat(w, additionalLabelValue) | ||||
| 		written += n | ||||
| 		if err != nil { | ||||
| 			return written, err | ||||
| 		} | ||||
| 		err = w.WriteByte('"') | ||||
| 		written++ | ||||
| 		if err != nil { | ||||
| 			return written, err | ||||
| 		} | ||||
| 	} | ||||
| 	err := w.WriteByte('}') | ||||
| 	written++ | ||||
| 	if err != nil { | ||||
| 		return written, err | ||||
| 	} | ||||
| 	return written, nil | ||||
| } | ||||
|  | ||||
| // writeEscapedString replaces '\' by '\\', new line character by '\n', and - if | ||||
| // includeDoubleQuote is true - '"' by '\"'. | ||||
| var ( | ||||
| 	escaper       = strings.NewReplacer("\\", `\\`, "\n", `\n`) | ||||
| 	quotedEscaper = strings.NewReplacer("\\", `\\`, "\n", `\n`, "\"", `\"`) | ||||
| ) | ||||
|  | ||||
| func writeEscapedString(w enhancedWriter, v string, includeDoubleQuote bool) (int, error) { | ||||
| 	if includeDoubleQuote { | ||||
| 		return quotedEscaper.WriteString(w, v) | ||||
| 	} | ||||
| 	return escaper.WriteString(w, v) | ||||
| } | ||||
|  | ||||
| // writeFloat is equivalent to fmt.Fprint with a float64 argument but hardcodes | ||||
| // a few common cases for increased efficiency. For non-hardcoded cases, it uses | ||||
| // strconv.AppendFloat to avoid allocations, similar to writeInt. | ||||
| func writeFloat(w enhancedWriter, f float64) (int, error) { | ||||
| 	switch { | ||||
| 	case f == 1: | ||||
| 		return 1, w.WriteByte('1') | ||||
| 	case f == 0: | ||||
| 		return 1, w.WriteByte('0') | ||||
| 	case f == -1: | ||||
| 		return w.WriteString("-1") | ||||
| 	case math.IsNaN(f): | ||||
| 		return w.WriteString("NaN") | ||||
| 	case math.IsInf(f, +1): | ||||
| 		return w.WriteString("+Inf") | ||||
| 	case math.IsInf(f, -1): | ||||
| 		return w.WriteString("-Inf") | ||||
| 	default: | ||||
| 		bp := numBufPool.Get().(*[]byte) | ||||
| 		*bp = strconv.AppendFloat((*bp)[:0], f, 'g', -1, 64) | ||||
| 		written, err := w.Write(*bp) | ||||
| 		numBufPool.Put(bp) | ||||
| 		return written, err | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // writeInt is equivalent to fmt.Fprint with an int64 argument but uses | ||||
| // strconv.AppendInt with a byte slice taken from a sync.Pool to avoid | ||||
| // allocations. | ||||
| func writeInt(w enhancedWriter, i int64) (int, error) { | ||||
| 	bp := numBufPool.Get().(*[]byte) | ||||
| 	*bp = strconv.AppendInt((*bp)[:0], i, 10) | ||||
| 	written, err := w.Write(*bp) | ||||
| 	numBufPool.Put(bp) | ||||
| 	return written, err | ||||
| } | ||||
							
								
								
									
										775
									
								
								vendor/github.com/prometheus/common/expfmt/text_parse.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										775
									
								
								vendor/github.com/prometheus/common/expfmt/text_parse.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,775 @@ | ||||
| // Copyright 2014 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 expfmt | ||||
|  | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"math" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
|  | ||||
| 	dto "github.com/prometheus/client_model/go" | ||||
|  | ||||
| 	"github.com/golang/protobuf/proto" //nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility. | ||||
| 	"github.com/prometheus/common/model" | ||||
| ) | ||||
|  | ||||
| // A stateFn is a function that represents a state in a state machine. By | ||||
| // executing it, the state is progressed to the next state. The stateFn returns | ||||
| // another stateFn, which represents the new state. The end state is represented | ||||
| // by nil. | ||||
| type stateFn func() stateFn | ||||
|  | ||||
| // ParseError signals errors while parsing the simple and flat text-based | ||||
| // exchange format. | ||||
| type ParseError struct { | ||||
| 	Line int | ||||
| 	Msg  string | ||||
| } | ||||
|  | ||||
| // Error implements the error interface. | ||||
| func (e ParseError) Error() string { | ||||
| 	return fmt.Sprintf("text format parsing error in line %d: %s", e.Line, e.Msg) | ||||
| } | ||||
|  | ||||
| // TextParser is used to parse the simple and flat text-based exchange format. Its | ||||
| // zero value is ready to use. | ||||
| type TextParser struct { | ||||
| 	metricFamiliesByName map[string]*dto.MetricFamily | ||||
| 	buf                  *bufio.Reader // Where the parsed input is read through. | ||||
| 	err                  error         // Most recent error. | ||||
| 	lineCount            int           // Tracks the line count for error messages. | ||||
| 	currentByte          byte          // The most recent byte read. | ||||
| 	currentToken         bytes.Buffer  // Re-used each time a token has to be gathered from multiple bytes. | ||||
| 	currentMF            *dto.MetricFamily | ||||
| 	currentMetric        *dto.Metric | ||||
| 	currentLabelPair     *dto.LabelPair | ||||
|  | ||||
| 	// The remaining member variables are only used for summaries/histograms. | ||||
| 	currentLabels map[string]string // All labels including '__name__' but excluding 'quantile'/'le' | ||||
| 	// Summary specific. | ||||
| 	summaries       map[uint64]*dto.Metric // Key is created with LabelsToSignature. | ||||
| 	currentQuantile float64 | ||||
| 	// Histogram specific. | ||||
| 	histograms    map[uint64]*dto.Metric // Key is created with LabelsToSignature. | ||||
| 	currentBucket float64 | ||||
| 	// These tell us if the currently processed line ends on '_count' or | ||||
| 	// '_sum' respectively and belong to a summary/histogram, representing the sample | ||||
| 	// count and sum of that summary/histogram. | ||||
| 	currentIsSummaryCount, currentIsSummarySum     bool | ||||
| 	currentIsHistogramCount, currentIsHistogramSum bool | ||||
| } | ||||
|  | ||||
| // TextToMetricFamilies reads 'in' as the simple and flat text-based exchange | ||||
| // format and creates MetricFamily proto messages. It returns the MetricFamily | ||||
| // proto messages in a map where the metric names are the keys, along with any | ||||
| // error encountered. | ||||
| // | ||||
| // If the input contains duplicate metrics (i.e. lines with the same metric name | ||||
| // and exactly the same label set), the resulting MetricFamily will contain | ||||
| // duplicate Metric proto messages. Similar is true for duplicate label | ||||
| // names. Checks for duplicates have to be performed separately, if required. | ||||
| // Also note that neither the metrics within each MetricFamily are sorted nor | ||||
| // the label pairs within each Metric. Sorting is not required for the most | ||||
| // frequent use of this method, which is sample ingestion in the Prometheus | ||||
| // server. However, for presentation purposes, you might want to sort the | ||||
| // metrics, and in some cases, you must sort the labels, e.g. for consumption by | ||||
| // the metric family injection hook of the Prometheus registry. | ||||
| // | ||||
| // Summaries and histograms are rather special beasts. You would probably not | ||||
| // use them in the simple text format anyway. This method can deal with | ||||
| // summaries and histograms if they are presented in exactly the way the | ||||
| // text.Create function creates them. | ||||
| // | ||||
| // This method must not be called concurrently. If you want to parse different | ||||
| // input concurrently, instantiate a separate Parser for each goroutine. | ||||
| func (p *TextParser) TextToMetricFamilies(in io.Reader) (map[string]*dto.MetricFamily, error) { | ||||
| 	p.reset(in) | ||||
| 	for nextState := p.startOfLine; nextState != nil; nextState = nextState() { | ||||
| 		// Magic happens here... | ||||
| 	} | ||||
| 	// Get rid of empty metric families. | ||||
| 	for k, mf := range p.metricFamiliesByName { | ||||
| 		if len(mf.GetMetric()) == 0 { | ||||
| 			delete(p.metricFamiliesByName, k) | ||||
| 		} | ||||
| 	} | ||||
| 	// If p.err is io.EOF now, we have run into a premature end of the input | ||||
| 	// 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 { | ||||
| 		p.parseError("unexpected end of input stream") | ||||
| 	} | ||||
| 	return p.metricFamiliesByName, p.err | ||||
| } | ||||
|  | ||||
| func (p *TextParser) reset(in io.Reader) { | ||||
| 	p.metricFamiliesByName = map[string]*dto.MetricFamily{} | ||||
| 	if p.buf == nil { | ||||
| 		p.buf = bufio.NewReader(in) | ||||
| 	} else { | ||||
| 		p.buf.Reset(in) | ||||
| 	} | ||||
| 	p.err = nil | ||||
| 	p.lineCount = 0 | ||||
| 	if p.summaries == nil || len(p.summaries) > 0 { | ||||
| 		p.summaries = map[uint64]*dto.Metric{} | ||||
| 	} | ||||
| 	if p.histograms == nil || len(p.histograms) > 0 { | ||||
| 		p.histograms = map[uint64]*dto.Metric{} | ||||
| 	} | ||||
| 	p.currentQuantile = math.NaN() | ||||
| 	p.currentBucket = math.NaN() | ||||
| } | ||||
|  | ||||
| // 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++ | ||||
| 	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 | ||||
| 		return nil | ||||
| 	} | ||||
| 	switch p.currentByte { | ||||
| 	case '#': | ||||
| 		return p.startComment | ||||
| 	case '\n': | ||||
| 		return p.startOfLine // Empty line, start the next one. | ||||
| 	} | ||||
| 	return p.readingMetricName | ||||
| } | ||||
|  | ||||
| // startComment represents the state where the next byte read from p.buf is the | ||||
| // start of a comment (or whitespace leading up to it). | ||||
| func (p *TextParser) startComment() stateFn { | ||||
| 	if p.skipBlankTab(); p.err != nil { | ||||
| 		return nil // Unexpected end of input. | ||||
| 	} | ||||
| 	if p.currentByte == '\n' { | ||||
| 		return p.startOfLine | ||||
| 	} | ||||
| 	if p.readTokenUntilWhitespace(); p.err != nil { | ||||
| 		return nil // Unexpected end of input. | ||||
| 	} | ||||
| 	// If we have hit the end of line already, there is nothing left | ||||
| 	// to do. This is not considered a syntax error. | ||||
| 	if p.currentByte == '\n' { | ||||
| 		return p.startOfLine | ||||
| 	} | ||||
| 	keyword := p.currentToken.String() | ||||
| 	if keyword != "HELP" && keyword != "TYPE" { | ||||
| 		// Generic comment, ignore by fast forwarding to end of line. | ||||
| 		for p.currentByte != '\n' { | ||||
| 			if p.currentByte, p.err = p.buf.ReadByte(); p.err != nil { | ||||
| 				return nil // Unexpected end of input. | ||||
| 			} | ||||
| 		} | ||||
| 		return p.startOfLine | ||||
| 	} | ||||
| 	// There is something. Next has to be a metric name. | ||||
| 	if p.skipBlankTab(); p.err != nil { | ||||
| 		return nil // Unexpected end of input. | ||||
| 	} | ||||
| 	if p.readTokenAsMetricName(); p.err != nil { | ||||
| 		return nil // Unexpected end of input. | ||||
| 	} | ||||
| 	if p.currentByte == '\n' { | ||||
| 		// At the end of the line already. | ||||
| 		// Again, this is not considered a syntax error. | ||||
| 		return p.startOfLine | ||||
| 	} | ||||
| 	if !isBlankOrTab(p.currentByte) { | ||||
| 		p.parseError("invalid metric name in comment") | ||||
| 		return nil | ||||
| 	} | ||||
| 	p.setOrCreateCurrentMF() | ||||
| 	if p.skipBlankTab(); p.err != nil { | ||||
| 		return nil // Unexpected end of input. | ||||
| 	} | ||||
| 	if p.currentByte == '\n' { | ||||
| 		// At the end of the line already. | ||||
| 		// Again, this is not considered a syntax error. | ||||
| 		return p.startOfLine | ||||
| 	} | ||||
| 	switch keyword { | ||||
| 	case "HELP": | ||||
| 		return p.readingHelp | ||||
| 	case "TYPE": | ||||
| 		return p.readingType | ||||
| 	} | ||||
| 	panic(fmt.Sprintf("code error: unexpected keyword %q", keyword)) | ||||
| } | ||||
|  | ||||
| // readingMetricName represents the state where the last byte read (now in | ||||
| // p.currentByte) is the first byte of a metric name. | ||||
| func (p *TextParser) readingMetricName() stateFn { | ||||
| 	if p.readTokenAsMetricName(); p.err != nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 	if p.currentToken.Len() == 0 { | ||||
| 		p.parseError("invalid metric name") | ||||
| 		return nil | ||||
| 	} | ||||
| 	p.setOrCreateCurrentMF() | ||||
| 	// 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() | ||||
| 	} | ||||
| 	p.currentMetric = &dto.Metric{} | ||||
| 	// Do not append the newly created currentMetric to | ||||
| 	// currentMF.Metric right now. First wait if this is a summary, | ||||
| 	// and the metric exists already, which we can only know after | ||||
| 	// having read all the labels. | ||||
| 	if p.skipBlankTabIfCurrentBlankTab(); p.err != nil { | ||||
| 		return nil // Unexpected end of input. | ||||
| 	} | ||||
| 	return p.readingLabels | ||||
| } | ||||
|  | ||||
| // readingLabels represents the state where the last byte read (now in | ||||
| // p.currentByte) is either the first byte of the label set (i.e. a '{'), or the | ||||
| // first byte of the value (otherwise). | ||||
| func (p *TextParser) readingLabels() stateFn { | ||||
| 	// Summaries/histograms are special. We have to reset the | ||||
| 	// currentLabels map, currentQuantile and currentBucket before starting to | ||||
| 	// read labels. | ||||
| 	if p.currentMF.GetType() == dto.MetricType_SUMMARY || p.currentMF.GetType() == dto.MetricType_HISTOGRAM { | ||||
| 		p.currentLabels = map[string]string{} | ||||
| 		p.currentLabels[string(model.MetricNameLabel)] = p.currentMF.GetName() | ||||
| 		p.currentQuantile = math.NaN() | ||||
| 		p.currentBucket = math.NaN() | ||||
| 	} | ||||
| 	if p.currentByte != '{' { | ||||
| 		return p.readingValue | ||||
| 	} | ||||
| 	return p.startLabelName | ||||
| } | ||||
|  | ||||
| // startLabelName represents the state where the next byte read from p.buf is | ||||
| // the start of a label name (or whitespace leading up to it). | ||||
| func (p *TextParser) startLabelName() stateFn { | ||||
| 	if p.skipBlankTab(); p.err != nil { | ||||
| 		return nil // Unexpected end of input. | ||||
| 	} | ||||
| 	if p.currentByte == '}' { | ||||
| 		if p.skipBlankTab(); p.err != nil { | ||||
| 			return nil // Unexpected end of input. | ||||
| 		} | ||||
| 		return p.readingValue | ||||
| 	} | ||||
| 	if p.readTokenAsLabelName(); p.err != nil { | ||||
| 		return nil // Unexpected end of input. | ||||
| 	} | ||||
| 	if p.currentToken.Len() == 0 { | ||||
| 		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 != '=' { | ||||
| 		p.parseError(fmt.Sprintf("expected '=' after label name, found %q", p.currentByte)) | ||||
| 		return nil | ||||
| 	} | ||||
| 	// Check for duplicate label names. | ||||
| 	labels := make(map[string]struct{}) | ||||
| 	for _, l := range p.currentMetric.Label { | ||||
| 		lName := l.GetName() | ||||
| 		if _, exists := labels[lName]; !exists { | ||||
| 			labels[lName] = struct{}{} | ||||
| 		} else { | ||||
| 			p.parseError(fmt.Sprintf("duplicate label names for metric %q", p.currentMF.GetName())) | ||||
| 			return nil | ||||
| 		} | ||||
| 	} | ||||
| 	return p.startLabelValue | ||||
| } | ||||
|  | ||||
| // startLabelValue represents the state where the next byte read from p.buf is | ||||
| // the start of a (quoted) label value (or whitespace leading up to it). | ||||
| func (p *TextParser) startLabelValue() stateFn { | ||||
| 	if p.skipBlankTab(); p.err != nil { | ||||
| 		return nil // Unexpected end of input. | ||||
| 	} | ||||
| 	if p.currentByte != '"' { | ||||
| 		p.parseError(fmt.Sprintf("expected '\"' at start of label value, found %q", p.currentByte)) | ||||
| 		return nil | ||||
| 	} | ||||
| 	if p.readTokenAsLabelValue(); p.err != nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 	if !model.LabelValue(p.currentToken.String()).IsValid() { | ||||
| 		p.parseError(fmt.Sprintf("invalid label value %q", p.currentToken.String())) | ||||
| 		return nil | ||||
| 	} | ||||
| 	p.currentLabelPair.Value = proto.String(p.currentToken.String()) | ||||
| 	// Special treatment of summaries: | ||||
| 	// - Quantile labels are special, will result in dto.Quantile later. | ||||
| 	// - Other labels have to be added to currentLabels for signature calculation. | ||||
| 	if p.currentMF.GetType() == dto.MetricType_SUMMARY { | ||||
| 		if p.currentLabelPair.GetName() == model.QuantileLabel { | ||||
| 			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())) | ||||
| 				return nil | ||||
| 			} | ||||
| 		} else { | ||||
| 			p.currentLabels[p.currentLabelPair.GetName()] = p.currentLabelPair.GetValue() | ||||
| 		} | ||||
| 	} | ||||
| 	// Similar special treatment of histograms. | ||||
| 	if p.currentMF.GetType() == dto.MetricType_HISTOGRAM { | ||||
| 		if p.currentLabelPair.GetName() == model.BucketLabel { | ||||
| 			if p.currentBucket, p.err = parseFloat(p.currentLabelPair.GetValue()); p.err != nil { | ||||
| 				// Create a more helpful error message. | ||||
| 				p.parseError(fmt.Sprintf("expected float as value for 'le' label, got %q", p.currentLabelPair.GetValue())) | ||||
| 				return nil | ||||
| 			} | ||||
| 		} else { | ||||
| 			p.currentLabels[p.currentLabelPair.GetName()] = p.currentLabelPair.GetValue() | ||||
| 		} | ||||
| 	} | ||||
| 	if p.skipBlankTab(); p.err != nil { | ||||
| 		return nil // Unexpected end of input. | ||||
| 	} | ||||
| 	switch p.currentByte { | ||||
| 	case ',': | ||||
| 		return p.startLabelName | ||||
|  | ||||
| 	case '}': | ||||
| 		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())) | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // readingValue represents the state where the last byte read (now in | ||||
| // p.currentByte) is the first byte of the sample value (i.e. a float). | ||||
| func (p *TextParser) readingValue() stateFn { | ||||
| 	// 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 { | ||||
| 		signature := model.LabelsToSignature(p.currentLabels) | ||||
| 		if summary := p.summaries[signature]; summary != nil { | ||||
| 			p.currentMetric = summary | ||||
| 		} else { | ||||
| 			p.summaries[signature] = p.currentMetric | ||||
| 			p.currentMF.Metric = append(p.currentMF.Metric, p.currentMetric) | ||||
| 		} | ||||
| 	} else if p.currentMF.GetType() == dto.MetricType_HISTOGRAM { | ||||
| 		signature := model.LabelsToSignature(p.currentLabels) | ||||
| 		if histogram := p.histograms[signature]; histogram != nil { | ||||
| 			p.currentMetric = histogram | ||||
| 		} else { | ||||
| 			p.histograms[signature] = p.currentMetric | ||||
| 			p.currentMF.Metric = append(p.currentMF.Metric, p.currentMetric) | ||||
| 		} | ||||
| 	} else { | ||||
| 		p.currentMF.Metric = append(p.currentMF.Metric, p.currentMetric) | ||||
| 	} | ||||
| 	if p.readTokenUntilWhitespace(); p.err != nil { | ||||
| 		return nil // Unexpected end of input. | ||||
| 	} | ||||
| 	value, err := parseFloat(p.currentToken.String()) | ||||
| 	if err != nil { | ||||
| 		// Create a more helpful error message. | ||||
| 		p.parseError(fmt.Sprintf("expected float as value, got %q", p.currentToken.String())) | ||||
| 		return nil | ||||
| 	} | ||||
| 	switch p.currentMF.GetType() { | ||||
| 	case dto.MetricType_COUNTER: | ||||
| 		p.currentMetric.Counter = &dto.Counter{Value: proto.Float64(value)} | ||||
| 	case dto.MetricType_GAUGE: | ||||
| 		p.currentMetric.Gauge = &dto.Gauge{Value: proto.Float64(value)} | ||||
| 	case dto.MetricType_UNTYPED: | ||||
| 		p.currentMetric.Untyped = &dto.Untyped{Value: proto.Float64(value)} | ||||
| 	case dto.MetricType_SUMMARY: | ||||
| 		// *sigh* | ||||
| 		if p.currentMetric.Summary == nil { | ||||
| 			p.currentMetric.Summary = &dto.Summary{} | ||||
| 		} | ||||
| 		switch { | ||||
| 		case p.currentIsSummaryCount: | ||||
| 			p.currentMetric.Summary.SampleCount = proto.Uint64(uint64(value)) | ||||
| 		case p.currentIsSummarySum: | ||||
| 			p.currentMetric.Summary.SampleSum = proto.Float64(value) | ||||
| 		case !math.IsNaN(p.currentQuantile): | ||||
| 			p.currentMetric.Summary.Quantile = append( | ||||
| 				p.currentMetric.Summary.Quantile, | ||||
| 				&dto.Quantile{ | ||||
| 					Quantile: proto.Float64(p.currentQuantile), | ||||
| 					Value:    proto.Float64(value), | ||||
| 				}, | ||||
| 			) | ||||
| 		} | ||||
| 	case dto.MetricType_HISTOGRAM: | ||||
| 		// *sigh* | ||||
| 		if p.currentMetric.Histogram == nil { | ||||
| 			p.currentMetric.Histogram = &dto.Histogram{} | ||||
| 		} | ||||
| 		switch { | ||||
| 		case p.currentIsHistogramCount: | ||||
| 			p.currentMetric.Histogram.SampleCount = proto.Uint64(uint64(value)) | ||||
| 		case p.currentIsHistogramSum: | ||||
| 			p.currentMetric.Histogram.SampleSum = proto.Float64(value) | ||||
| 		case !math.IsNaN(p.currentBucket): | ||||
| 			p.currentMetric.Histogram.Bucket = append( | ||||
| 				p.currentMetric.Histogram.Bucket, | ||||
| 				&dto.Bucket{ | ||||
| 					UpperBound:      proto.Float64(p.currentBucket), | ||||
| 					CumulativeCount: proto.Uint64(uint64(value)), | ||||
| 				}, | ||||
| 			) | ||||
| 		} | ||||
| 	default: | ||||
| 		p.err = fmt.Errorf("unexpected type for metric name %q", p.currentMF.GetName()) | ||||
| 	} | ||||
| 	if p.currentByte == '\n' { | ||||
| 		return p.startOfLine | ||||
| 	} | ||||
| 	return p.startTimestamp | ||||
| } | ||||
|  | ||||
| // startTimestamp represents the state where the next byte read from p.buf is | ||||
| // the start of the timestamp (or whitespace leading up to it). | ||||
| func (p *TextParser) startTimestamp() stateFn { | ||||
| 	if p.skipBlankTab(); p.err != nil { | ||||
| 		return nil // Unexpected end of input. | ||||
| 	} | ||||
| 	if p.readTokenUntilWhitespace(); p.err != nil { | ||||
| 		return nil // Unexpected end of input. | ||||
| 	} | ||||
| 	timestamp, err := strconv.ParseInt(p.currentToken.String(), 10, 64) | ||||
| 	if err != nil { | ||||
| 		// Create a more helpful error message. | ||||
| 		p.parseError(fmt.Sprintf("expected integer as timestamp, got %q", p.currentToken.String())) | ||||
| 		return nil | ||||
| 	} | ||||
| 	p.currentMetric.TimestampMs = proto.Int64(timestamp) | ||||
| 	if p.readTokenUntilNewline(false); p.err != nil { | ||||
| 		return nil // Unexpected end of input. | ||||
| 	} | ||||
| 	if p.currentToken.Len() > 0 { | ||||
| 		p.parseError(fmt.Sprintf("spurious string after timestamp: %q", p.currentToken.String())) | ||||
| 		return nil | ||||
| 	} | ||||
| 	return p.startOfLine | ||||
| } | ||||
|  | ||||
| // readingHelp represents the state where the last byte read (now in | ||||
| // p.currentByte) is the first byte of the docstring after 'HELP'. | ||||
| func (p *TextParser) readingHelp() stateFn { | ||||
| 	if p.currentMF.Help != nil { | ||||
| 		p.parseError(fmt.Sprintf("second HELP line for metric name %q", p.currentMF.GetName())) | ||||
| 		return nil | ||||
| 	} | ||||
| 	// Rest of line is the docstring. | ||||
| 	if p.readTokenUntilNewline(true); p.err != nil { | ||||
| 		return nil // Unexpected end of input. | ||||
| 	} | ||||
| 	p.currentMF.Help = proto.String(p.currentToken.String()) | ||||
| 	return p.startOfLine | ||||
| } | ||||
|  | ||||
| // readingType represents the state where the last byte read (now in | ||||
| // p.currentByte) is the first byte of the type hint after 'HELP'. | ||||
| func (p *TextParser) readingType() stateFn { | ||||
| 	if p.currentMF.Type != nil { | ||||
| 		p.parseError(fmt.Sprintf("second TYPE line for metric name %q, or TYPE reported after samples", p.currentMF.GetName())) | ||||
| 		return nil | ||||
| 	} | ||||
| 	// Rest of line is the type. | ||||
| 	if p.readTokenUntilNewline(false); p.err != nil { | ||||
| 		return nil // Unexpected end of input. | ||||
| 	} | ||||
| 	metricType, ok := dto.MetricType_value[strings.ToUpper(p.currentToken.String())] | ||||
| 	if !ok { | ||||
| 		p.parseError(fmt.Sprintf("unknown metric type %q", p.currentToken.String())) | ||||
| 		return nil | ||||
| 	} | ||||
| 	p.currentMF.Type = dto.MetricType(metricType).Enum() | ||||
| 	return p.startOfLine | ||||
| } | ||||
|  | ||||
| // parseError sets p.err to a ParseError at the current line with the given | ||||
| // message. | ||||
| func (p *TextParser) parseError(msg string) { | ||||
| 	p.err = ParseError{ | ||||
| 		Line: p.lineCount, | ||||
| 		Msg:  msg, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // skipBlankTab reads (and discards) bytes from p.buf until it encounters a byte | ||||
| // that is neither ' ' nor '\t'. That byte is left in p.currentByte. | ||||
| func (p *TextParser) skipBlankTab() { | ||||
| 	for { | ||||
| 		if p.currentByte, p.err = p.buf.ReadByte(); p.err != nil || !isBlankOrTab(p.currentByte) { | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // skipBlankTabIfCurrentBlankTab works exactly as skipBlankTab but doesn't do | ||||
| // anything if p.currentByte is neither ' ' nor '\t'. | ||||
| func (p *TextParser) skipBlankTabIfCurrentBlankTab() { | ||||
| 	if isBlankOrTab(p.currentByte) { | ||||
| 		p.skipBlankTab() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // readTokenUntilWhitespace copies bytes from p.buf into p.currentToken.  The | ||||
| // first byte considered is the byte already read (now in p.currentByte).  The | ||||
| // first whitespace byte encountered is still copied into p.currentByte, but not | ||||
| // into p.currentToken. | ||||
| func (p *TextParser) readTokenUntilWhitespace() { | ||||
| 	p.currentToken.Reset() | ||||
| 	for p.err == nil && !isBlankOrTab(p.currentByte) && p.currentByte != '\n' { | ||||
| 		p.currentToken.WriteByte(p.currentByte) | ||||
| 		p.currentByte, p.err = p.buf.ReadByte() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // readTokenUntilNewline copies bytes from p.buf into p.currentToken.  The first | ||||
| // byte considered is the byte already read (now in p.currentByte).  The first | ||||
| // newline byte encountered is still copied into p.currentByte, but not into | ||||
| // p.currentToken. If recognizeEscapeSequence is true, two escape sequences are | ||||
| // recognized: '\\' translates into '\', and '\n' into a line-feed character. | ||||
| // All other escape sequences are invalid and cause an error. | ||||
| func (p *TextParser) readTokenUntilNewline(recognizeEscapeSequence bool) { | ||||
| 	p.currentToken.Reset() | ||||
| 	escaped := false | ||||
| 	for p.err == nil { | ||||
| 		if recognizeEscapeSequence && escaped { | ||||
| 			switch p.currentByte { | ||||
| 			case '\\': | ||||
| 				p.currentToken.WriteByte(p.currentByte) | ||||
| 			case 'n': | ||||
| 				p.currentToken.WriteByte('\n') | ||||
| 			default: | ||||
| 				p.parseError(fmt.Sprintf("invalid escape sequence '\\%c'", p.currentByte)) | ||||
| 				return | ||||
| 			} | ||||
| 			escaped = false | ||||
| 		} else { | ||||
| 			switch p.currentByte { | ||||
| 			case '\n': | ||||
| 				return | ||||
| 			case '\\': | ||||
| 				escaped = true | ||||
| 			default: | ||||
| 				p.currentToken.WriteByte(p.currentByte) | ||||
| 			} | ||||
| 		} | ||||
| 		p.currentByte, p.err = p.buf.ReadByte() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // readTokenAsMetricName copies a metric name from p.buf into p.currentToken. | ||||
| // The first byte considered is the byte already read (now in p.currentByte). | ||||
| // The first byte not part of a metric name is still copied into p.currentByte, | ||||
| // but not into p.currentToken. | ||||
| func (p *TextParser) readTokenAsMetricName() { | ||||
| 	p.currentToken.Reset() | ||||
| 	if !isValidMetricNameStart(p.currentByte) { | ||||
| 		return | ||||
| 	} | ||||
| 	for { | ||||
| 		p.currentToken.WriteByte(p.currentByte) | ||||
| 		p.currentByte, p.err = p.buf.ReadByte() | ||||
| 		if p.err != nil || !isValidMetricNameContinuation(p.currentByte) { | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // readTokenAsLabelName copies a label name from p.buf into p.currentToken. | ||||
| // The first byte considered is the byte already read (now in p.currentByte). | ||||
| // The first byte not part of a label name is still copied into p.currentByte, | ||||
| // but not into p.currentToken. | ||||
| func (p *TextParser) readTokenAsLabelName() { | ||||
| 	p.currentToken.Reset() | ||||
| 	if !isValidLabelNameStart(p.currentByte) { | ||||
| 		return | ||||
| 	} | ||||
| 	for { | ||||
| 		p.currentToken.WriteByte(p.currentByte) | ||||
| 		p.currentByte, p.err = p.buf.ReadByte() | ||||
| 		if p.err != nil || !isValidLabelNameContinuation(p.currentByte) { | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // readTokenAsLabelValue copies a label value from p.buf into p.currentToken. | ||||
| // In contrast to the other 'readTokenAs...' functions, which start with the | ||||
| // last read byte in p.currentByte, this method ignores p.currentByte and starts | ||||
| // with reading a new byte from p.buf. The first byte not part of a label value | ||||
| // is still copied into p.currentByte, but not into p.currentToken. | ||||
| func (p *TextParser) readTokenAsLabelValue() { | ||||
| 	p.currentToken.Reset() | ||||
| 	escaped := false | ||||
| 	for { | ||||
| 		if p.currentByte, p.err = p.buf.ReadByte(); p.err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 		if escaped { | ||||
| 			switch p.currentByte { | ||||
| 			case '"', '\\': | ||||
| 				p.currentToken.WriteByte(p.currentByte) | ||||
| 			case 'n': | ||||
| 				p.currentToken.WriteByte('\n') | ||||
| 			default: | ||||
| 				p.parseError(fmt.Sprintf("invalid escape sequence '\\%c'", p.currentByte)) | ||||
| 				return | ||||
| 			} | ||||
| 			escaped = false | ||||
| 			continue | ||||
| 		} | ||||
| 		switch p.currentByte { | ||||
| 		case '"': | ||||
| 			return | ||||
| 		case '\n': | ||||
| 			p.parseError(fmt.Sprintf("label value %q contains unescaped new-line", p.currentToken.String())) | ||||
| 			return | ||||
| 		case '\\': | ||||
| 			escaped = true | ||||
| 		default: | ||||
| 			p.currentToken.WriteByte(p.currentByte) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (p *TextParser) setOrCreateCurrentMF() { | ||||
| 	p.currentIsSummaryCount = false | ||||
| 	p.currentIsSummarySum = false | ||||
| 	p.currentIsHistogramCount = false | ||||
| 	p.currentIsHistogramSum = false | ||||
| 	name := p.currentToken.String() | ||||
| 	if p.currentMF = p.metricFamiliesByName[name]; p.currentMF != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	// Try out if this is a _sum or _count for a summary/histogram. | ||||
| 	summaryName := summaryMetricName(name) | ||||
| 	if p.currentMF = p.metricFamiliesByName[summaryName]; p.currentMF != nil { | ||||
| 		if p.currentMF.GetType() == dto.MetricType_SUMMARY { | ||||
| 			if isCount(name) { | ||||
| 				p.currentIsSummaryCount = true | ||||
| 			} | ||||
| 			if isSum(name) { | ||||
| 				p.currentIsSummarySum = true | ||||
| 			} | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	histogramName := histogramMetricName(name) | ||||
| 	if p.currentMF = p.metricFamiliesByName[histogramName]; p.currentMF != nil { | ||||
| 		if p.currentMF.GetType() == dto.MetricType_HISTOGRAM { | ||||
| 			if isCount(name) { | ||||
| 				p.currentIsHistogramCount = true | ||||
| 			} | ||||
| 			if isSum(name) { | ||||
| 				p.currentIsHistogramSum = true | ||||
| 			} | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	p.currentMF = &dto.MetricFamily{Name: proto.String(name)} | ||||
| 	p.metricFamiliesByName[name] = p.currentMF | ||||
| } | ||||
|  | ||||
| func isValidLabelNameStart(b byte) bool { | ||||
| 	return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' | ||||
| } | ||||
|  | ||||
| func isValidLabelNameContinuation(b byte) bool { | ||||
| 	return isValidLabelNameStart(b) || (b >= '0' && b <= '9') | ||||
| } | ||||
|  | ||||
| func isValidMetricNameStart(b byte) bool { | ||||
| 	return isValidLabelNameStart(b) || b == ':' | ||||
| } | ||||
|  | ||||
| func isValidMetricNameContinuation(b byte) bool { | ||||
| 	return isValidLabelNameContinuation(b) || b == ':' | ||||
| } | ||||
|  | ||||
| func isBlankOrTab(b byte) bool { | ||||
| 	return b == ' ' || b == '\t' | ||||
| } | ||||
|  | ||||
| func isCount(name string) bool { | ||||
| 	return len(name) > 6 && name[len(name)-6:] == "_count" | ||||
| } | ||||
|  | ||||
| func isSum(name string) bool { | ||||
| 	return len(name) > 4 && name[len(name)-4:] == "_sum" | ||||
| } | ||||
|  | ||||
| func isBucket(name string) bool { | ||||
| 	return len(name) > 7 && name[len(name)-7:] == "_bucket" | ||||
| } | ||||
|  | ||||
| func summaryMetricName(name string) string { | ||||
| 	switch { | ||||
| 	case isCount(name): | ||||
| 		return name[:len(name)-6] | ||||
| 	case isSum(name): | ||||
| 		return name[:len(name)-4] | ||||
| 	default: | ||||
| 		return name | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func histogramMetricName(name string) string { | ||||
| 	switch { | ||||
| 	case isCount(name): | ||||
| 		return name[:len(name)-6] | ||||
| 	case isSum(name): | ||||
| 		return name[:len(name)-4] | ||||
| 	case isBucket(name): | ||||
| 		return name[:len(name)-7] | ||||
| 	default: | ||||
| 		return name | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func parseFloat(s string) (float64, error) { | ||||
| 	if strings.ContainsAny(s, "pP_") { | ||||
| 		return 0, fmt.Errorf("unsupported character in float") | ||||
| 	} | ||||
| 	return strconv.ParseFloat(s, 64) | ||||
| } | ||||
		Reference in New Issue
	
	Block a user