// 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 (
	
	
	
	
	
	
	

	

	dto 
)

// 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(io.Discard)
		},
	}
	numBufPool = sync.Pool{
		New: func() interface{} {
			 := make([]byte, 0, initialNumBufSize)
			return &
		},
	}
)

// 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.
//
// If metric names conform to the legacy validation pattern, they will be placed
// outside the brackets in the traditional way, like `foo{}`. If the metric name
// fails the legacy validation check, it will be placed quoted inside the
// brackets: `{"foo"}`. As stated above, the input is assumed to be santized and
// no error will be thrown in this case.
//
// Similar to metric names, if label names conform to the legacy validation
// pattern, they will be unquoted as normal, like `foo{bar="baz"}`. If the label
// name fails the legacy validation check, it will be quoted:
// `foo{"bar"="baz"}`. As stated above, the input is assumed to be santized and
// no error will be thrown in this case.
//
// This method fulfills the type 'prometheus.encoder'.
func ( io.Writer,  *dto.MetricFamily) ( int,  error) {
	// Fail-fast checks.
	if len(.Metric) == 0 {
		return 0, fmt.Errorf("MetricFamily has no metrics: %s", )
	}
	 := .GetName()
	if  == "" {
		return 0, fmt.Errorf("MetricFamily has no name: %s", )
	}

	// Try the interface upgrade. If it doesn't work, we'll use a
	// bufio.Writer from the sync.Pool.
	,  := .(enhancedWriter)
	if ! {
		 := bufPool.Get().(*bufio.Writer)
		.Reset()
		 = 
		defer func() {
			 := .Flush()
			if  == nil {
				 = 
			}
			bufPool.Put()
		}()
	}

	var  int

	// Comments, first HELP, then TYPE.
	if .Help != nil {
		,  = .WriteString("# HELP ")
		 += 
		if  != nil {
			return
		}
		,  = writeName(, )
		 += 
		if  != nil {
			return
		}
		 = .WriteByte(' ')
		++
		if  != nil {
			return
		}
		,  = writeEscapedString(, *.Help, false)
		 += 
		if  != nil {
			return
		}
		 = .WriteByte('\n')
		++
		if  != nil {
			return
		}
	}
	,  = .WriteString("# TYPE ")
	 += 
	if  != nil {
		return
	}
	,  = writeName(, )
	 += 
	if  != nil {
		return
	}
	 := .GetType()
	switch  {
	case dto.MetricType_COUNTER:
		,  = .WriteString(" counter\n")
	case dto.MetricType_GAUGE:
		,  = .WriteString(" gauge\n")
	case dto.MetricType_SUMMARY:
		,  = .WriteString(" summary\n")
	case dto.MetricType_UNTYPED:
		,  = .WriteString(" untyped\n")
	case dto.MetricType_HISTOGRAM:
		,  = .WriteString(" histogram\n")
	default:
		return , fmt.Errorf("unknown metric type %s", .String())
	}
	 += 
	if  != nil {
		return
	}

	// Finally the samples, one line for each.
	for ,  := range .Metric {
		switch  {
		case dto.MetricType_COUNTER:
			if .Counter == nil {
				return , fmt.Errorf(
					"expected counter in metric %s %s", , ,
				)
			}
			,  = writeSample(
				, , "", , "", 0,
				.Counter.GetValue(),
			)
		case dto.MetricType_GAUGE:
			if .Gauge == nil {
				return , fmt.Errorf(
					"expected gauge in metric %s %s", , ,
				)
			}
			,  = writeSample(
				, , "", , "", 0,
				.Gauge.GetValue(),
			)
		case dto.MetricType_UNTYPED:
			if .Untyped == nil {
				return , fmt.Errorf(
					"expected untyped in metric %s %s", , ,
				)
			}
			,  = writeSample(
				, , "", , "", 0,
				.Untyped.GetValue(),
			)
		case dto.MetricType_SUMMARY:
			if .Summary == nil {
				return , fmt.Errorf(
					"expected summary in metric %s %s", , ,
				)
			}
			for ,  := range .Summary.Quantile {
				,  = writeSample(
					, , "", ,
					model.QuantileLabel, .GetQuantile(),
					.GetValue(),
				)
				 += 
				if  != nil {
					return
				}
			}
			,  = writeSample(
				, , "_sum", , "", 0,
				.Summary.GetSampleSum(),
			)
			 += 
			if  != nil {
				return
			}
			,  = writeSample(
				, , "_count", , "", 0,
				float64(.Summary.GetSampleCount()),
			)
		case dto.MetricType_HISTOGRAM:
			if .Histogram == nil {
				return , fmt.Errorf(
					"expected histogram in metric %s %s", , ,
				)
			}
			 := false
			for ,  := range .Histogram.Bucket {
				,  = writeSample(
					, , "_bucket", ,
					model.BucketLabel, .GetUpperBound(),
					float64(.GetCumulativeCount()),
				)
				 += 
				if  != nil {
					return
				}
				if math.IsInf(.GetUpperBound(), +1) {
					 = true
				}
			}
			if ! {
				,  = writeSample(
					, , "_bucket", ,
					model.BucketLabel, math.Inf(+1),
					float64(.Histogram.GetSampleCount()),
				)
				 += 
				if  != nil {
					return
				}
			}
			,  = writeSample(
				, , "_sum", , "", 0,
				.Histogram.GetSampleSum(),
			)
			 += 
			if  != nil {
				return
			}
			,  = writeSample(
				, , "_count", , "", 0,
				float64(.Histogram.GetSampleCount()),
			)
		default:
			return , fmt.Errorf(
				"unexpected type in metric %s %s", , ,
			)
		}
		 += 
		if  != 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(
	 enhancedWriter,
	,  string,
	 *dto.Metric,
	 string,  float64,
	 float64,
) (int, error) {
	 := 0
	,  := writeNameAndLabelPairs(
		, +, .Label, , ,
	)
	 += 
	if  != nil {
		return , 
	}
	 = .WriteByte(' ')
	++
	if  != nil {
		return , 
	}
	,  = writeFloat(, )
	 += 
	if  != nil {
		return , 
	}
	if .TimestampMs != nil {
		 = .WriteByte(' ')
		++
		if  != nil {
			return , 
		}
		,  = writeInt(, *.TimestampMs)
		 += 
		if  != nil {
			return , 
		}
	}
	 = .WriteByte('\n')
	++
	if  != nil {
		return , 
	}
	return , nil
}

// writeNameAndLabelPairs converts a slice of LabelPair proto messages plus the
// explicitly given metric name and additional label pair into text formatted as
// required by the text format and writes it to 'w'. An empty slice in
// combination with an empty string 'additionalLabelName' results in nothing
// being written. Otherwise, the label pairs are written, escaped as required by
// the text format, and enclosed in '{...}'. The function returns the number of
// bytes written and any error encountered. If the metric name is not
// legacy-valid, it will be put inside the brackets as well. Legacy-invalid
// label names will also be quoted.
func writeNameAndLabelPairs(
	 enhancedWriter,
	 string,
	 []*dto.LabelPair,
	 string,  float64,
) (int, error) {
	var (
		            int
		          byte = '{'
		      = false
	)

	if  != "" {
		// If the name does not pass the legacy validity check, we must put the
		// metric name inside the braces.
		if !model.IsValidLegacyMetricName() {
			 = true
			 := .WriteByte()
			++
			if  != nil {
				return , 
			}
			 = ','
		}
		,  := writeName(, )
		 += 
		if  != nil {
			return , 
		}
	}

	if len() == 0 &&  == "" {
		if  {
			 := .WriteByte('}')
			++
			if  != nil {
				return , 
			}
		}
		return , nil
	}

	for ,  := range  {
		 := .WriteByte()
		++
		if  != nil {
			return , 
		}
		,  := writeName(, .GetName())
		 += 
		if  != nil {
			return , 
		}
		,  = .WriteString(`="`)
		 += 
		if  != nil {
			return , 
		}
		,  = writeEscapedString(, .GetValue(), true)
		 += 
		if  != nil {
			return , 
		}
		 = .WriteByte('"')
		++
		if  != nil {
			return , 
		}
		 = ','
	}
	if  != "" {
		 := .WriteByte()
		++
		if  != nil {
			return , 
		}
		,  := .WriteString()
		 += 
		if  != nil {
			return , 
		}
		,  = .WriteString(`="`)
		 += 
		if  != nil {
			return , 
		}
		,  = writeFloat(, )
		 += 
		if  != nil {
			return , 
		}
		 = .WriteByte('"')
		++
		if  != nil {
			return , 
		}
	}
	 := .WriteByte('}')
	++
	if  != nil {
		return , 
	}
	return , 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( enhancedWriter,  string,  bool) (int, error) {
	if  {
		return quotedEscaper.WriteString(, )
	}
	return escaper.WriteString(, )
}

// 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( enhancedWriter,  float64) (int, error) {
	switch {
	case  == 1:
		return 1, .WriteByte('1')
	case  == 0:
		return 1, .WriteByte('0')
	case  == -1:
		return .WriteString("-1")
	case math.IsNaN():
		return .WriteString("NaN")
	case math.IsInf(, +1):
		return .WriteString("+Inf")
	case math.IsInf(, -1):
		return .WriteString("-Inf")
	default:
		 := numBufPool.Get().(*[]byte)
		* = strconv.AppendFloat((*)[:0], , 'g', -1, 64)
		,  := .Write(*)
		numBufPool.Put()
		return , 
	}
}

// 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( enhancedWriter,  int64) (int, error) {
	 := numBufPool.Get().(*[]byte)
	* = strconv.AppendInt((*)[:0], , 10)
	,  := .Write(*)
	numBufPool.Put()
	return , 
}

// writeName writes a string as-is if it complies with the legacy naming
// scheme, or escapes it in double quotes if not.
func writeName( enhancedWriter,  string) (int, error) {
	if model.IsValidLegacyMetricName() {
		return .WriteString()
	}
	var  int
	var  error
	 = .WriteByte('"')
	++
	if  != nil {
		return , 
	}
	var  int
	,  = writeEscapedString(, , true)
	 += 
	if  != nil {
		return , 
	}
	 = .WriteByte('"')
	++
	return , 
}