github.com/hellobchain/third_party@v0.0.0-20230331131523-deb0478a2e52/prometheus/common/expfmt/text_parse.go (about)

     1  // Copyright 2014 The Prometheus Authors
     2  // Licensed under the Apache License, Version 2.0 (the "License");
     3  // you may not use this file except in compliance with the License.
     4  // You may obtain a copy of the License at
     5  //
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package expfmt
    15  
    16  import (
    17  	"bufio"
    18  	"bytes"
    19  	"fmt"
    20  	"io"
    21  	"math"
    22  	"strconv"
    23  	"strings"
    24  
    25  	dto "github.com/prometheus/client_model/go"
    26  
    27  	"github.com/golang/protobuf/proto"
    28  	"github.com/hellobchain/third_party/prometheus/common/model"
    29  )
    30  
    31  // A stateFn is a function that represents a state in a state machine. By
    32  // executing it, the state is progressed to the next state. The stateFn returns
    33  // another stateFn, which represents the new state. The end state is represented
    34  // by nil.
    35  type stateFn func() stateFn
    36  
    37  // ParseError signals errors while parsing the simple and flat text-based
    38  // exchange format.
    39  type ParseError struct {
    40  	Line int
    41  	Msg  string
    42  }
    43  
    44  // Error implements the error interface.
    45  func (e ParseError) Error() string {
    46  	return fmt.Sprintf("text format parsing error in line %d: %s", e.Line, e.Msg)
    47  }
    48  
    49  // TextParser is used to parse the simple and flat text-based exchange format. Its
    50  // zero value is ready to use.
    51  type TextParser struct {
    52  	metricFamiliesByName map[string]*dto.MetricFamily
    53  	buf                  *bufio.Reader // Where the parsed input is read through.
    54  	err                  error         // Most recent error.
    55  	lineCount            int           // Tracks the line count for error messages.
    56  	currentByte          byte          // The most recent byte read.
    57  	currentToken         bytes.Buffer  // Re-used each time a token has to be gathered from multiple bytes.
    58  	currentMF            *dto.MetricFamily
    59  	currentMetric        *dto.Metric
    60  	currentLabelPair     *dto.LabelPair
    61  
    62  	// The remaining member variables are only used for summaries/histograms.
    63  	currentLabels map[string]string // All labels including '__name__' but excluding 'quantile'/'le'
    64  	// Summary specific.
    65  	summaries       map[uint64]*dto.Metric // Key is created with LabelsToSignature.
    66  	currentQuantile float64
    67  	// Histogram specific.
    68  	histograms    map[uint64]*dto.Metric // Key is created with LabelsToSignature.
    69  	currentBucket float64
    70  	// These tell us if the currently processed line ends on '_count' or
    71  	// '_sum' respectively and belong to a summary/histogram, representing the sample
    72  	// count and sum of that summary/histogram.
    73  	currentIsSummaryCount, currentIsSummarySum     bool
    74  	currentIsHistogramCount, currentIsHistogramSum bool
    75  }
    76  
    77  // TextToMetricFamilies reads 'in' as the simple and flat text-based exchange
    78  // format and creates MetricFamily proto messages. It returns the MetricFamily
    79  // proto messages in a map where the metric names are the keys, along with any
    80  // error encountered.
    81  //
    82  // If the input contains duplicate metrics (i.e. lines with the same metric name
    83  // and exactly the same label set), the resulting MetricFamily will contain
    84  // duplicate Metric proto messages. Similar is true for duplicate label
    85  // names. Checks for duplicates have to be performed separately, if required.
    86  // Also note that neither the metrics within each MetricFamily are sorted nor
    87  // the label pairs within each Metric. Sorting is not required for the most
    88  // frequent use of this method, which is sample ingestion in the Prometheus
    89  // server. However, for presentation purposes, you might want to sort the
    90  // metrics, and in some cases, you must sort the labels, e.g. for consumption by
    91  // the metric family injection hook of the Prometheus registry.
    92  //
    93  // Summaries and histograms are rather special beasts. You would probably not
    94  // use them in the simple text format anyway. This method can deal with
    95  // summaries and histograms if they are presented in exactly the way the
    96  // text.Create function creates them.
    97  //
    98  // This method must not be called concurrently. If you want to parse different
    99  // input concurrently, instantiate a separate Parser for each goroutine.
   100  func (p *TextParser) TextToMetricFamilies(in io.Reader) (map[string]*dto.MetricFamily, error) {
   101  	p.reset(in)
   102  	for nextState := p.startOfLine; nextState != nil; nextState = nextState() {
   103  		// Magic happens here...
   104  	}
   105  	// Get rid of empty metric families.
   106  	for k, mf := range p.metricFamiliesByName {
   107  		if len(mf.GetMetric()) == 0 {
   108  			delete(p.metricFamiliesByName, k)
   109  		}
   110  	}
   111  	// If p.err is io.EOF now, we have run into a premature end of the input
   112  	// stream. Turn this error into something nicer and more
   113  	// meaningful. (io.EOF is often used as a signal for the legitimate end
   114  	// of an input stream.)
   115  	if p.err == io.EOF {
   116  		p.parseError("unexpected end of input stream")
   117  	}
   118  	return p.metricFamiliesByName, p.err
   119  }
   120  
   121  func (p *TextParser) reset(in io.Reader) {
   122  	p.metricFamiliesByName = map[string]*dto.MetricFamily{}
   123  	if p.buf == nil {
   124  		p.buf = bufio.NewReader(in)
   125  	} else {
   126  		p.buf.Reset(in)
   127  	}
   128  	p.err = nil
   129  	p.lineCount = 0
   130  	if p.summaries == nil || len(p.summaries) > 0 {
   131  		p.summaries = map[uint64]*dto.Metric{}
   132  	}
   133  	if p.histograms == nil || len(p.histograms) > 0 {
   134  		p.histograms = map[uint64]*dto.Metric{}
   135  	}
   136  	p.currentQuantile = math.NaN()
   137  	p.currentBucket = math.NaN()
   138  }
   139  
   140  // startOfLine represents the state where the next byte read from p.buf is the
   141  // start of a line (or whitespace leading up to it).
   142  func (p *TextParser) startOfLine() stateFn {
   143  	p.lineCount++
   144  	if p.skipBlankTab(); p.err != nil {
   145  		// End of input reached. This is the only case where
   146  		// that is not an error but a signal that we are done.
   147  		p.err = nil
   148  		return nil
   149  	}
   150  	switch p.currentByte {
   151  	case '#':
   152  		return p.startComment
   153  	case '\n':
   154  		return p.startOfLine // Empty line, start the next one.
   155  	}
   156  	return p.readingMetricName
   157  }
   158  
   159  // startComment represents the state where the next byte read from p.buf is the
   160  // start of a comment (or whitespace leading up to it).
   161  func (p *TextParser) startComment() stateFn {
   162  	if p.skipBlankTab(); p.err != nil {
   163  		return nil // Unexpected end of input.
   164  	}
   165  	if p.currentByte == '\n' {
   166  		return p.startOfLine
   167  	}
   168  	if p.readTokenUntilWhitespace(); p.err != nil {
   169  		return nil // Unexpected end of input.
   170  	}
   171  	// If we have hit the end of line already, there is nothing left
   172  	// to do. This is not considered a syntax error.
   173  	if p.currentByte == '\n' {
   174  		return p.startOfLine
   175  	}
   176  	keyword := p.currentToken.String()
   177  	if keyword != "HELP" && keyword != "TYPE" {
   178  		// Generic comment, ignore by fast forwarding to end of line.
   179  		for p.currentByte != '\n' {
   180  			if p.currentByte, p.err = p.buf.ReadByte(); p.err != nil {
   181  				return nil // Unexpected end of input.
   182  			}
   183  		}
   184  		return p.startOfLine
   185  	}
   186  	// There is something. Next has to be a metric name.
   187  	if p.skipBlankTab(); p.err != nil {
   188  		return nil // Unexpected end of input.
   189  	}
   190  	if p.readTokenAsMetricName(); p.err != nil {
   191  		return nil // Unexpected end of input.
   192  	}
   193  	if p.currentByte == '\n' {
   194  		// At the end of the line already.
   195  		// Again, this is not considered a syntax error.
   196  		return p.startOfLine
   197  	}
   198  	if !isBlankOrTab(p.currentByte) {
   199  		p.parseError("invalid metric name in comment")
   200  		return nil
   201  	}
   202  	p.setOrCreateCurrentMF()
   203  	if p.skipBlankTab(); p.err != nil {
   204  		return nil // Unexpected end of input.
   205  	}
   206  	if p.currentByte == '\n' {
   207  		// At the end of the line already.
   208  		// Again, this is not considered a syntax error.
   209  		return p.startOfLine
   210  	}
   211  	switch keyword {
   212  	case "HELP":
   213  		return p.readingHelp
   214  	case "TYPE":
   215  		return p.readingType
   216  	}
   217  	panic(fmt.Sprintf("code error: unexpected keyword %q", keyword))
   218  }
   219  
   220  // readingMetricName represents the state where the last byte read (now in
   221  // p.currentByte) is the first byte of a metric name.
   222  func (p *TextParser) readingMetricName() stateFn {
   223  	if p.readTokenAsMetricName(); p.err != nil {
   224  		return nil
   225  	}
   226  	if p.currentToken.Len() == 0 {
   227  		p.parseError("invalid metric name")
   228  		return nil
   229  	}
   230  	p.setOrCreateCurrentMF()
   231  	// Now is the time to fix the type if it hasn't happened yet.
   232  	if p.currentMF.Type == nil {
   233  		p.currentMF.Type = dto.MetricType_UNTYPED.Enum()
   234  	}
   235  	p.currentMetric = &dto.Metric{}
   236  	// Do not append the newly created currentMetric to
   237  	// currentMF.Metric right now. First wait if this is a summary,
   238  	// and the metric exists already, which we can only know after
   239  	// having read all the labels.
   240  	if p.skipBlankTabIfCurrentBlankTab(); p.err != nil {
   241  		return nil // Unexpected end of input.
   242  	}
   243  	return p.readingLabels
   244  }
   245  
   246  // readingLabels represents the state where the last byte read (now in
   247  // p.currentByte) is either the first byte of the label set (i.e. a '{'), or the
   248  // first byte of the value (otherwise).
   249  func (p *TextParser) readingLabels() stateFn {
   250  	// Summaries/histograms are special. We have to reset the
   251  	// currentLabels map, currentQuantile and currentBucket before starting to
   252  	// read labels.
   253  	if p.currentMF.GetType() == dto.MetricType_SUMMARY || p.currentMF.GetType() == dto.MetricType_HISTOGRAM {
   254  		p.currentLabels = map[string]string{}
   255  		p.currentLabels[string(model.MetricNameLabel)] = p.currentMF.GetName()
   256  		p.currentQuantile = math.NaN()
   257  		p.currentBucket = math.NaN()
   258  	}
   259  	if p.currentByte != '{' {
   260  		return p.readingValue
   261  	}
   262  	return p.startLabelName
   263  }
   264  
   265  // startLabelName represents the state where the next byte read from p.buf is
   266  // the start of a label name (or whitespace leading up to it).
   267  func (p *TextParser) startLabelName() stateFn {
   268  	if p.skipBlankTab(); p.err != nil {
   269  		return nil // Unexpected end of input.
   270  	}
   271  	if p.currentByte == '}' {
   272  		if p.skipBlankTab(); p.err != nil {
   273  			return nil // Unexpected end of input.
   274  		}
   275  		return p.readingValue
   276  	}
   277  	if p.readTokenAsLabelName(); p.err != nil {
   278  		return nil // Unexpected end of input.
   279  	}
   280  	if p.currentToken.Len() == 0 {
   281  		p.parseError(fmt.Sprintf("invalid label name for metric %q", p.currentMF.GetName()))
   282  		return nil
   283  	}
   284  	p.currentLabelPair = &dto.LabelPair{Name: proto.String(p.currentToken.String())}
   285  	if p.currentLabelPair.GetName() == string(model.MetricNameLabel) {
   286  		p.parseError(fmt.Sprintf("label name %q is reserved", model.MetricNameLabel))
   287  		return nil
   288  	}
   289  	// Special summary/histogram treatment. Don't add 'quantile' and 'le'
   290  	// labels to 'real' labels.
   291  	if !(p.currentMF.GetType() == dto.MetricType_SUMMARY && p.currentLabelPair.GetName() == model.QuantileLabel) &&
   292  		!(p.currentMF.GetType() == dto.MetricType_HISTOGRAM && p.currentLabelPair.GetName() == model.BucketLabel) {
   293  		p.currentMetric.Label = append(p.currentMetric.Label, p.currentLabelPair)
   294  	}
   295  	if p.skipBlankTabIfCurrentBlankTab(); p.err != nil {
   296  		return nil // Unexpected end of input.
   297  	}
   298  	if p.currentByte != '=' {
   299  		p.parseError(fmt.Sprintf("expected '=' after label name, found %q", p.currentByte))
   300  		return nil
   301  	}
   302  	return p.startLabelValue
   303  }
   304  
   305  // startLabelValue represents the state where the next byte read from p.buf is
   306  // the start of a (quoted) label value (or whitespace leading up to it).
   307  func (p *TextParser) startLabelValue() stateFn {
   308  	if p.skipBlankTab(); p.err != nil {
   309  		return nil // Unexpected end of input.
   310  	}
   311  	if p.currentByte != '"' {
   312  		p.parseError(fmt.Sprintf("expected '\"' at start of label value, found %q", p.currentByte))
   313  		return nil
   314  	}
   315  	if p.readTokenAsLabelValue(); p.err != nil {
   316  		return nil
   317  	}
   318  	if !model.LabelValue(p.currentToken.String()).IsValid() {
   319  		p.parseError(fmt.Sprintf("invalid label value %q", p.currentToken.String()))
   320  		return nil
   321  	}
   322  	p.currentLabelPair.Value = proto.String(p.currentToken.String())
   323  	// Special treatment of summaries:
   324  	// - Quantile labels are special, will result in dto.Quantile later.
   325  	// - Other labels have to be added to currentLabels for signature calculation.
   326  	if p.currentMF.GetType() == dto.MetricType_SUMMARY {
   327  		if p.currentLabelPair.GetName() == model.QuantileLabel {
   328  			if p.currentQuantile, p.err = strconv.ParseFloat(p.currentLabelPair.GetValue(), 64); p.err != nil {
   329  				// Create a more helpful error message.
   330  				p.parseError(fmt.Sprintf("expected float as value for 'quantile' label, got %q", p.currentLabelPair.GetValue()))
   331  				return nil
   332  			}
   333  		} else {
   334  			p.currentLabels[p.currentLabelPair.GetName()] = p.currentLabelPair.GetValue()
   335  		}
   336  	}
   337  	// Similar special treatment of histograms.
   338  	if p.currentMF.GetType() == dto.MetricType_HISTOGRAM {
   339  		if p.currentLabelPair.GetName() == model.BucketLabel {
   340  			if p.currentBucket, p.err = strconv.ParseFloat(p.currentLabelPair.GetValue(), 64); p.err != nil {
   341  				// Create a more helpful error message.
   342  				p.parseError(fmt.Sprintf("expected float as value for 'le' label, got %q", p.currentLabelPair.GetValue()))
   343  				return nil
   344  			}
   345  		} else {
   346  			p.currentLabels[p.currentLabelPair.GetName()] = p.currentLabelPair.GetValue()
   347  		}
   348  	}
   349  	if p.skipBlankTab(); p.err != nil {
   350  		return nil // Unexpected end of input.
   351  	}
   352  	switch p.currentByte {
   353  	case ',':
   354  		return p.startLabelName
   355  
   356  	case '}':
   357  		if p.skipBlankTab(); p.err != nil {
   358  			return nil // Unexpected end of input.
   359  		}
   360  		return p.readingValue
   361  	default:
   362  		p.parseError(fmt.Sprintf("unexpected end of label value %q", p.currentLabelPair.GetValue()))
   363  		return nil
   364  	}
   365  }
   366  
   367  // readingValue represents the state where the last byte read (now in
   368  // p.currentByte) is the first byte of the sample value (i.e. a float).
   369  func (p *TextParser) readingValue() stateFn {
   370  	// When we are here, we have read all the labels, so for the
   371  	// special case of a summary/histogram, we can finally find out
   372  	// if the metric already exists.
   373  	if p.currentMF.GetType() == dto.MetricType_SUMMARY {
   374  		signature := model.LabelsToSignature(p.currentLabels)
   375  		if summary := p.summaries[signature]; summary != nil {
   376  			p.currentMetric = summary
   377  		} else {
   378  			p.summaries[signature] = p.currentMetric
   379  			p.currentMF.Metric = append(p.currentMF.Metric, p.currentMetric)
   380  		}
   381  	} else if p.currentMF.GetType() == dto.MetricType_HISTOGRAM {
   382  		signature := model.LabelsToSignature(p.currentLabels)
   383  		if histogram := p.histograms[signature]; histogram != nil {
   384  			p.currentMetric = histogram
   385  		} else {
   386  			p.histograms[signature] = p.currentMetric
   387  			p.currentMF.Metric = append(p.currentMF.Metric, p.currentMetric)
   388  		}
   389  	} else {
   390  		p.currentMF.Metric = append(p.currentMF.Metric, p.currentMetric)
   391  	}
   392  	if p.readTokenUntilWhitespace(); p.err != nil {
   393  		return nil // Unexpected end of input.
   394  	}
   395  	value, err := strconv.ParseFloat(p.currentToken.String(), 64)
   396  	if err != nil {
   397  		// Create a more helpful error message.
   398  		p.parseError(fmt.Sprintf("expected float as value, got %q", p.currentToken.String()))
   399  		return nil
   400  	}
   401  	switch p.currentMF.GetType() {
   402  	case dto.MetricType_COUNTER:
   403  		p.currentMetric.Counter = &dto.Counter{Value: proto.Float64(value)}
   404  	case dto.MetricType_GAUGE:
   405  		p.currentMetric.Gauge = &dto.Gauge{Value: proto.Float64(value)}
   406  	case dto.MetricType_UNTYPED:
   407  		p.currentMetric.Untyped = &dto.Untyped{Value: proto.Float64(value)}
   408  	case dto.MetricType_SUMMARY:
   409  		// *sigh*
   410  		if p.currentMetric.Summary == nil {
   411  			p.currentMetric.Summary = &dto.Summary{}
   412  		}
   413  		switch {
   414  		case p.currentIsSummaryCount:
   415  			p.currentMetric.Summary.SampleCount = proto.Uint64(uint64(value))
   416  		case p.currentIsSummarySum:
   417  			p.currentMetric.Summary.SampleSum = proto.Float64(value)
   418  		case !math.IsNaN(p.currentQuantile):
   419  			p.currentMetric.Summary.Quantile = append(
   420  				p.currentMetric.Summary.Quantile,
   421  				&dto.Quantile{
   422  					Quantile: proto.Float64(p.currentQuantile),
   423  					Value:    proto.Float64(value),
   424  				},
   425  			)
   426  		}
   427  	case dto.MetricType_HISTOGRAM:
   428  		// *sigh*
   429  		if p.currentMetric.Histogram == nil {
   430  			p.currentMetric.Histogram = &dto.Histogram{}
   431  		}
   432  		switch {
   433  		case p.currentIsHistogramCount:
   434  			p.currentMetric.Histogram.SampleCount = proto.Uint64(uint64(value))
   435  		case p.currentIsHistogramSum:
   436  			p.currentMetric.Histogram.SampleSum = proto.Float64(value)
   437  		case !math.IsNaN(p.currentBucket):
   438  			p.currentMetric.Histogram.Bucket = append(
   439  				p.currentMetric.Histogram.Bucket,
   440  				&dto.Bucket{
   441  					UpperBound:      proto.Float64(p.currentBucket),
   442  					CumulativeCount: proto.Uint64(uint64(value)),
   443  				},
   444  			)
   445  		}
   446  	default:
   447  		p.err = fmt.Errorf("unexpected type for metric name %q", p.currentMF.GetName())
   448  	}
   449  	if p.currentByte == '\n' {
   450  		return p.startOfLine
   451  	}
   452  	return p.startTimestamp
   453  }
   454  
   455  // startTimestamp represents the state where the next byte read from p.buf is
   456  // the start of the timestamp (or whitespace leading up to it).
   457  func (p *TextParser) startTimestamp() stateFn {
   458  	if p.skipBlankTab(); p.err != nil {
   459  		return nil // Unexpected end of input.
   460  	}
   461  	if p.readTokenUntilWhitespace(); p.err != nil {
   462  		return nil // Unexpected end of input.
   463  	}
   464  	timestamp, err := strconv.ParseInt(p.currentToken.String(), 10, 64)
   465  	if err != nil {
   466  		// Create a more helpful error message.
   467  		p.parseError(fmt.Sprintf("expected integer as timestamp, got %q", p.currentToken.String()))
   468  		return nil
   469  	}
   470  	p.currentMetric.TimestampMs = proto.Int64(timestamp)
   471  	if p.readTokenUntilNewline(false); p.err != nil {
   472  		return nil // Unexpected end of input.
   473  	}
   474  	if p.currentToken.Len() > 0 {
   475  		p.parseError(fmt.Sprintf("spurious string after timestamp: %q", p.currentToken.String()))
   476  		return nil
   477  	}
   478  	return p.startOfLine
   479  }
   480  
   481  // readingHelp represents the state where the last byte read (now in
   482  // p.currentByte) is the first byte of the docstring after 'HELP'.
   483  func (p *TextParser) readingHelp() stateFn {
   484  	if p.currentMF.Help != nil {
   485  		p.parseError(fmt.Sprintf("second HELP line for metric name %q", p.currentMF.GetName()))
   486  		return nil
   487  	}
   488  	// Rest of line is the docstring.
   489  	if p.readTokenUntilNewline(true); p.err != nil {
   490  		return nil // Unexpected end of input.
   491  	}
   492  	p.currentMF.Help = proto.String(p.currentToken.String())
   493  	return p.startOfLine
   494  }
   495  
   496  // readingType represents the state where the last byte read (now in
   497  // p.currentByte) is the first byte of the type hint after 'HELP'.
   498  func (p *TextParser) readingType() stateFn {
   499  	if p.currentMF.Type != nil {
   500  		p.parseError(fmt.Sprintf("second TYPE line for metric name %q, or TYPE reported after samples", p.currentMF.GetName()))
   501  		return nil
   502  	}
   503  	// Rest of line is the type.
   504  	if p.readTokenUntilNewline(false); p.err != nil {
   505  		return nil // Unexpected end of input.
   506  	}
   507  	metricType, ok := dto.MetricType_value[strings.ToUpper(p.currentToken.String())]
   508  	if !ok {
   509  		p.parseError(fmt.Sprintf("unknown metric type %q", p.currentToken.String()))
   510  		return nil
   511  	}
   512  	p.currentMF.Type = dto.MetricType(metricType).Enum()
   513  	return p.startOfLine
   514  }
   515  
   516  // parseError sets p.err to a ParseError at the current line with the given
   517  // message.
   518  func (p *TextParser) parseError(msg string) {
   519  	p.err = ParseError{
   520  		Line: p.lineCount,
   521  		Msg:  msg,
   522  	}
   523  }
   524  
   525  // skipBlankTab reads (and discards) bytes from p.buf until it encounters a byte
   526  // that is neither ' ' nor '\t'. That byte is left in p.currentByte.
   527  func (p *TextParser) skipBlankTab() {
   528  	for {
   529  		if p.currentByte, p.err = p.buf.ReadByte(); p.err != nil || !isBlankOrTab(p.currentByte) {
   530  			return
   531  		}
   532  	}
   533  }
   534  
   535  // skipBlankTabIfCurrentBlankTab works exactly as skipBlankTab but doesn't do
   536  // anything if p.currentByte is neither ' ' nor '\t'.
   537  func (p *TextParser) skipBlankTabIfCurrentBlankTab() {
   538  	if isBlankOrTab(p.currentByte) {
   539  		p.skipBlankTab()
   540  	}
   541  }
   542  
   543  // readTokenUntilWhitespace copies bytes from p.buf into p.currentToken.  The
   544  // first byte considered is the byte already read (now in p.currentByte).  The
   545  // first whitespace byte encountered is still copied into p.currentByte, but not
   546  // into p.currentToken.
   547  func (p *TextParser) readTokenUntilWhitespace() {
   548  	p.currentToken.Reset()
   549  	for p.err == nil && !isBlankOrTab(p.currentByte) && p.currentByte != '\n' {
   550  		p.currentToken.WriteByte(p.currentByte)
   551  		p.currentByte, p.err = p.buf.ReadByte()
   552  	}
   553  }
   554  
   555  // readTokenUntilNewline copies bytes from p.buf into p.currentToken.  The first
   556  // byte considered is the byte already read (now in p.currentByte).  The first
   557  // newline byte encountered is still copied into p.currentByte, but not into
   558  // p.currentToken. If recognizeEscapeSequence is true, two escape sequences are
   559  // recognized: '\\' translates into '\', and '\n' into a line-feed character.
   560  // All other escape sequences are invalid and cause an error.
   561  func (p *TextParser) readTokenUntilNewline(recognizeEscapeSequence bool) {
   562  	p.currentToken.Reset()
   563  	escaped := false
   564  	for p.err == nil {
   565  		if recognizeEscapeSequence && escaped {
   566  			switch p.currentByte {
   567  			case '\\':
   568  				p.currentToken.WriteByte(p.currentByte)
   569  			case 'n':
   570  				p.currentToken.WriteByte('\n')
   571  			default:
   572  				p.parseError(fmt.Sprintf("invalid escape sequence '\\%c'", p.currentByte))
   573  				return
   574  			}
   575  			escaped = false
   576  		} else {
   577  			switch p.currentByte {
   578  			case '\n':
   579  				return
   580  			case '\\':
   581  				escaped = true
   582  			default:
   583  				p.currentToken.WriteByte(p.currentByte)
   584  			}
   585  		}
   586  		p.currentByte, p.err = p.buf.ReadByte()
   587  	}
   588  }
   589  
   590  // readTokenAsMetricName copies a metric name from p.buf into p.currentToken.
   591  // The first byte considered is the byte already read (now in p.currentByte).
   592  // The first byte not part of a metric name is still copied into p.currentByte,
   593  // but not into p.currentToken.
   594  func (p *TextParser) readTokenAsMetricName() {
   595  	p.currentToken.Reset()
   596  	if !isValidMetricNameStart(p.currentByte) {
   597  		return
   598  	}
   599  	for {
   600  		p.currentToken.WriteByte(p.currentByte)
   601  		p.currentByte, p.err = p.buf.ReadByte()
   602  		if p.err != nil || !isValidMetricNameContinuation(p.currentByte) {
   603  			return
   604  		}
   605  	}
   606  }
   607  
   608  // readTokenAsLabelName copies a label name from p.buf into p.currentToken.
   609  // The first byte considered is the byte already read (now in p.currentByte).
   610  // The first byte not part of a label name is still copied into p.currentByte,
   611  // but not into p.currentToken.
   612  func (p *TextParser) readTokenAsLabelName() {
   613  	p.currentToken.Reset()
   614  	if !isValidLabelNameStart(p.currentByte) {
   615  		return
   616  	}
   617  	for {
   618  		p.currentToken.WriteByte(p.currentByte)
   619  		p.currentByte, p.err = p.buf.ReadByte()
   620  		if p.err != nil || !isValidLabelNameContinuation(p.currentByte) {
   621  			return
   622  		}
   623  	}
   624  }
   625  
   626  // readTokenAsLabelValue copies a label value from p.buf into p.currentToken.
   627  // In contrast to the other 'readTokenAs...' functions, which start with the
   628  // last read byte in p.currentByte, this method ignores p.currentByte and starts
   629  // with reading a new byte from p.buf. The first byte not part of a label value
   630  // is still copied into p.currentByte, but not into p.currentToken.
   631  func (p *TextParser) readTokenAsLabelValue() {
   632  	p.currentToken.Reset()
   633  	escaped := false
   634  	for {
   635  		if p.currentByte, p.err = p.buf.ReadByte(); p.err != nil {
   636  			return
   637  		}
   638  		if escaped {
   639  			switch p.currentByte {
   640  			case '"', '\\':
   641  				p.currentToken.WriteByte(p.currentByte)
   642  			case 'n':
   643  				p.currentToken.WriteByte('\n')
   644  			default:
   645  				p.parseError(fmt.Sprintf("invalid escape sequence '\\%c'", p.currentByte))
   646  				return
   647  			}
   648  			escaped = false
   649  			continue
   650  		}
   651  		switch p.currentByte {
   652  		case '"':
   653  			return
   654  		case '\n':
   655  			p.parseError(fmt.Sprintf("label value %q contains unescaped new-line", p.currentToken.String()))
   656  			return
   657  		case '\\':
   658  			escaped = true
   659  		default:
   660  			p.currentToken.WriteByte(p.currentByte)
   661  		}
   662  	}
   663  }
   664  
   665  func (p *TextParser) setOrCreateCurrentMF() {
   666  	p.currentIsSummaryCount = false
   667  	p.currentIsSummarySum = false
   668  	p.currentIsHistogramCount = false
   669  	p.currentIsHistogramSum = false
   670  	name := p.currentToken.String()
   671  	if p.currentMF = p.metricFamiliesByName[name]; p.currentMF != nil {
   672  		return
   673  	}
   674  	// Try out if this is a _sum or _count for a summary/histogram.
   675  	summaryName := summaryMetricName(name)
   676  	if p.currentMF = p.metricFamiliesByName[summaryName]; p.currentMF != nil {
   677  		if p.currentMF.GetType() == dto.MetricType_SUMMARY {
   678  			if isCount(name) {
   679  				p.currentIsSummaryCount = true
   680  			}
   681  			if isSum(name) {
   682  				p.currentIsSummarySum = true
   683  			}
   684  			return
   685  		}
   686  	}
   687  	histogramName := histogramMetricName(name)
   688  	if p.currentMF = p.metricFamiliesByName[histogramName]; p.currentMF != nil {
   689  		if p.currentMF.GetType() == dto.MetricType_HISTOGRAM {
   690  			if isCount(name) {
   691  				p.currentIsHistogramCount = true
   692  			}
   693  			if isSum(name) {
   694  				p.currentIsHistogramSum = true
   695  			}
   696  			return
   697  		}
   698  	}
   699  	p.currentMF = &dto.MetricFamily{Name: proto.String(name)}
   700  	p.metricFamiliesByName[name] = p.currentMF
   701  }
   702  
   703  func isValidLabelNameStart(b byte) bool {
   704  	return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_'
   705  }
   706  
   707  func isValidLabelNameContinuation(b byte) bool {
   708  	return isValidLabelNameStart(b) || (b >= '0' && b <= '9')
   709  }
   710  
   711  func isValidMetricNameStart(b byte) bool {
   712  	return isValidLabelNameStart(b) || b == ':'
   713  }
   714  
   715  func isValidMetricNameContinuation(b byte) bool {
   716  	return isValidLabelNameContinuation(b) || b == ':'
   717  }
   718  
   719  func isBlankOrTab(b byte) bool {
   720  	return b == ' ' || b == '\t'
   721  }
   722  
   723  func isCount(name string) bool {
   724  	return len(name) > 6 && name[len(name)-6:] == "_count"
   725  }
   726  
   727  func isSum(name string) bool {
   728  	return len(name) > 4 && name[len(name)-4:] == "_sum"
   729  }
   730  
   731  func isBucket(name string) bool {
   732  	return len(name) > 7 && name[len(name)-7:] == "_bucket"
   733  }
   734  
   735  func summaryMetricName(name string) string {
   736  	switch {
   737  	case isCount(name):
   738  		return name[:len(name)-6]
   739  	case isSum(name):
   740  		return name[:len(name)-4]
   741  	default:
   742  		return name
   743  	}
   744  }
   745  
   746  func histogramMetricName(name string) string {
   747  	switch {
   748  	case isCount(name):
   749  		return name[:len(name)-6]
   750  	case isSum(name):
   751  		return name[:len(name)-4]
   752  	case isBucket(name):
   753  		return name[:len(name)-7]
   754  	default:
   755  		return name
   756  	}
   757  }