github.com/netdata/go.d.plugin@v0.58.1/pkg/prometheus/parse.go (about)

     1  package prometheus
     2  
     3  import (
     4  	"errors"
     5  	"io"
     6  	"regexp"
     7  	"strconv"
     8  	"strings"
     9  
    10  	"github.com/netdata/go.d.plugin/pkg/prometheus/selector"
    11  
    12  	"github.com/prometheus/prometheus/model/labels"
    13  	"github.com/prometheus/prometheus/model/textparse"
    14  )
    15  
    16  const (
    17  	quantileLabel = "quantile"
    18  	bucketLabel   = "le"
    19  )
    20  
    21  const (
    22  	countSuffix  = "_count"
    23  	sumSuffix    = "_sum"
    24  	bucketSuffix = "_bucket"
    25  )
    26  
    27  type promTextParser struct {
    28  	metrics MetricFamilies
    29  	series  Series
    30  
    31  	sr selector.Selector
    32  
    33  	currMF     *MetricFamily
    34  	currSeries labels.Labels
    35  
    36  	summaries  map[uint64]*Summary
    37  	histograms map[uint64]*Histogram
    38  
    39  	isCount    bool
    40  	isSum      bool
    41  	isQuantile bool
    42  	isBucket   bool
    43  
    44  	currQuantile float64
    45  	currBucket   float64
    46  }
    47  
    48  func (p *promTextParser) parseToSeries(text []byte) (Series, error) {
    49  	p.series.Reset()
    50  
    51  	parser := textparse.NewPromParser(text)
    52  	for {
    53  		entry, err := parser.Next()
    54  		if err != nil {
    55  			if errors.Is(err, io.EOF) {
    56  				break
    57  			}
    58  			if entry == textparse.EntryInvalid && strings.HasPrefix(err.Error(), "invalid metric type") {
    59  				continue
    60  			}
    61  			return nil, err
    62  		}
    63  
    64  		switch entry {
    65  		case textparse.EntrySeries:
    66  			p.currSeries = p.currSeries[:0]
    67  
    68  			parser.Metric(&p.currSeries)
    69  
    70  			if p.sr != nil && !p.sr.Matches(p.currSeries) {
    71  				continue
    72  			}
    73  
    74  			_, _, val := parser.Series()
    75  			p.series.Add(SeriesSample{Labels: copyLabels(p.currSeries), Value: val})
    76  		}
    77  	}
    78  
    79  	p.series.Sort()
    80  
    81  	return p.series, nil
    82  }
    83  
    84  var reSpace = regexp.MustCompile(`\s+`)
    85  
    86  func (p *promTextParser) parseToMetricFamilies(text []byte) (MetricFamilies, error) {
    87  	p.reset()
    88  
    89  	parser := textparse.NewPromParser(text)
    90  	for {
    91  		entry, err := parser.Next()
    92  		if err != nil {
    93  			if errors.Is(err, io.EOF) {
    94  				break
    95  			}
    96  			if entry == textparse.EntryInvalid && strings.HasPrefix(err.Error(), "invalid metric type") {
    97  				continue
    98  			}
    99  			return nil, err
   100  		}
   101  
   102  		switch entry {
   103  		case textparse.EntryHelp:
   104  			name, help := parser.Help()
   105  			p.setMetricFamilyByName(string(name))
   106  			p.currMF.help = string(help)
   107  			if strings.IndexByte(p.currMF.help, '\n') != -1 {
   108  				// convert multiline to one line because HELP is used as the chart title.
   109  				p.currMF.help = reSpace.ReplaceAllString(strings.TrimSpace(p.currMF.help), " ")
   110  			}
   111  		case textparse.EntryType:
   112  			name, typ := parser.Type()
   113  			p.setMetricFamilyByName(string(name))
   114  			p.currMF.typ = typ
   115  		case textparse.EntrySeries:
   116  			p.currSeries = p.currSeries[:0]
   117  
   118  			parser.Metric(&p.currSeries)
   119  
   120  			if p.sr != nil && !p.sr.Matches(p.currSeries) {
   121  				continue
   122  			}
   123  
   124  			p.setMetricFamilyBySeries()
   125  
   126  			_, _, value := parser.Series()
   127  
   128  			switch p.currMF.typ {
   129  			case textparse.MetricTypeGauge:
   130  				p.addGauge(value)
   131  			case textparse.MetricTypeCounter:
   132  				p.addCounter(value)
   133  			case textparse.MetricTypeSummary:
   134  				p.addSummary(value)
   135  			case textparse.MetricTypeHistogram:
   136  				p.addHistogram(value)
   137  			case textparse.MetricTypeUnknown:
   138  				p.addUnknown(value)
   139  			}
   140  		}
   141  	}
   142  
   143  	for k, v := range p.metrics {
   144  		if len(v.Metrics()) == 0 {
   145  			delete(p.metrics, k)
   146  		}
   147  	}
   148  
   149  	return p.metrics, nil
   150  }
   151  
   152  func (p *promTextParser) setMetricFamilyByName(name string) {
   153  	mf, ok := p.metrics[name]
   154  	if !ok {
   155  		mf = &MetricFamily{name: name, typ: textparse.MetricTypeUnknown}
   156  		p.metrics[name] = mf
   157  	}
   158  	p.currMF = mf
   159  }
   160  
   161  func (p *promTextParser) setMetricFamilyBySeries() {
   162  	p.isSum, p.isCount, p.isQuantile, p.isBucket = false, false, false, false
   163  	p.currQuantile, p.currBucket = 0, 0
   164  
   165  	name := p.currSeries[0].Value
   166  
   167  	if p.currMF != nil && p.currMF.name == name {
   168  		if p.currMF.typ == textparse.MetricTypeSummary {
   169  			p.setQuantile()
   170  		}
   171  		return
   172  	}
   173  
   174  	typ := textparse.MetricTypeUnknown
   175  
   176  	switch {
   177  	case strings.HasSuffix(name, sumSuffix):
   178  		n := strings.TrimSuffix(name, sumSuffix)
   179  		if mf, ok := p.metrics[n]; ok && isSummaryOrHistogram(mf.typ) {
   180  			p.isSum = true
   181  			p.currSeries[0].Value = n
   182  			p.currMF = mf
   183  			return
   184  		}
   185  	case strings.HasSuffix(name, countSuffix):
   186  		n := strings.TrimSuffix(name, countSuffix)
   187  		if mf, ok := p.metrics[n]; ok && isSummaryOrHistogram(mf.typ) {
   188  			p.isCount = true
   189  			p.currSeries[0].Value = n
   190  			p.currMF = mf
   191  			return
   192  		}
   193  	case strings.HasSuffix(name, bucketSuffix):
   194  		n := strings.TrimSuffix(name, bucketSuffix)
   195  		if mf, ok := p.metrics[n]; ok && isSummaryOrHistogram(mf.typ) {
   196  			p.currSeries[0].Value = n
   197  			p.setBucket()
   198  			p.currMF = mf
   199  			return
   200  		}
   201  		if p.currSeries.Has(bucketLabel) {
   202  			p.currSeries[0].Value = n
   203  			p.setBucket()
   204  			name = n
   205  			typ = textparse.MetricTypeHistogram
   206  		}
   207  	case p.currSeries.Has(quantileLabel):
   208  		typ = textparse.MetricTypeSummary
   209  		p.setQuantile()
   210  	}
   211  
   212  	p.setMetricFamilyByName(name)
   213  	if p.currMF.typ == "" || p.currMF.typ == textparse.MetricTypeUnknown {
   214  		p.currMF.typ = typ
   215  	}
   216  }
   217  
   218  func (p *promTextParser) setQuantile() {
   219  	if lbs, v, ok := removeLabel(p.currSeries, quantileLabel); ok {
   220  		p.isQuantile = true
   221  		p.currSeries = lbs
   222  		p.currQuantile, _ = strconv.ParseFloat(v, 64)
   223  	}
   224  }
   225  
   226  func (p *promTextParser) setBucket() {
   227  	if lbs, v, ok := removeLabel(p.currSeries, bucketLabel); ok {
   228  		p.isBucket = true
   229  		p.currSeries = lbs
   230  		p.currBucket, _ = strconv.ParseFloat(v, 64)
   231  	}
   232  }
   233  
   234  func (p *promTextParser) addGauge(value float64) {
   235  	p.currSeries = p.currSeries[1:] // remove "__name__"
   236  
   237  	if v := len(p.currMF.metrics); v == cap(p.currMF.metrics) {
   238  		p.currMF.metrics = append(p.currMF.metrics, Metric{
   239  			labels: copyLabels(p.currSeries),
   240  			gauge:  &Gauge{value: value},
   241  		})
   242  	} else {
   243  		p.currMF.metrics = p.currMF.metrics[:v+1]
   244  		if p.currMF.metrics[v].gauge == nil {
   245  			p.currMF.metrics[v].gauge = &Gauge{}
   246  		}
   247  		p.currMF.metrics[v].gauge.value = value
   248  		p.currMF.metrics[v].labels = p.currMF.metrics[v].labels[:0]
   249  		p.currMF.metrics[v].labels = append(p.currMF.metrics[v].labels, p.currSeries...)
   250  	}
   251  }
   252  
   253  func (p *promTextParser) addCounter(value float64) {
   254  	p.currSeries = p.currSeries[1:] // remove "__name__"
   255  
   256  	if v := len(p.currMF.metrics); v == cap(p.currMF.metrics) {
   257  		p.currMF.metrics = append(p.currMF.metrics, Metric{
   258  			labels:  copyLabels(p.currSeries),
   259  			counter: &Counter{value: value},
   260  		})
   261  	} else {
   262  		p.currMF.metrics = p.currMF.metrics[:v+1]
   263  		if p.currMF.metrics[v].counter == nil {
   264  			p.currMF.metrics[v].counter = &Counter{}
   265  		}
   266  		p.currMF.metrics[v].counter.value = value
   267  		p.currMF.metrics[v].labels = p.currMF.metrics[v].labels[:0]
   268  		p.currMF.metrics[v].labels = append(p.currMF.metrics[v].labels, p.currSeries...)
   269  	}
   270  }
   271  
   272  func (p *promTextParser) addUnknown(value float64) {
   273  	p.currSeries = p.currSeries[1:] // remove "__name__"
   274  
   275  	if v := len(p.currMF.metrics); v == cap(p.currMF.metrics) {
   276  		p.currMF.metrics = append(p.currMF.metrics, Metric{
   277  			labels:  copyLabels(p.currSeries),
   278  			untyped: &Untyped{value: value},
   279  		})
   280  	} else {
   281  		p.currMF.metrics = p.currMF.metrics[:v+1]
   282  		if p.currMF.metrics[v].untyped == nil {
   283  			p.currMF.metrics[v].untyped = &Untyped{}
   284  		}
   285  		p.currMF.metrics[v].untyped.value = value
   286  		p.currMF.metrics[v].labels = p.currMF.metrics[v].labels[:0]
   287  		p.currMF.metrics[v].labels = append(p.currMF.metrics[v].labels, p.currSeries...)
   288  	}
   289  }
   290  
   291  func (p *promTextParser) addSummary(value float64) {
   292  	hash := p.currSeries.Hash()
   293  
   294  	p.currSeries = p.currSeries[1:] // remove "__name__"
   295  
   296  	s, ok := p.summaries[hash]
   297  	if !ok {
   298  		if v := len(p.currMF.metrics); v == cap(p.currMF.metrics) {
   299  			s = &Summary{}
   300  			p.currMF.metrics = append(p.currMF.metrics, Metric{
   301  				labels:  copyLabels(p.currSeries),
   302  				summary: s,
   303  			})
   304  		} else {
   305  			p.currMF.metrics = p.currMF.metrics[:v+1]
   306  			if p.currMF.metrics[v].summary == nil {
   307  				p.currMF.metrics[v].summary = &Summary{}
   308  			}
   309  			p.currMF.metrics[v].summary.sum = 0
   310  			p.currMF.metrics[v].summary.count = 0
   311  			p.currMF.metrics[v].summary.quantiles = p.currMF.metrics[v].summary.quantiles[:0]
   312  			p.currMF.metrics[v].labels = p.currMF.metrics[v].labels[:0]
   313  			p.currMF.metrics[v].labels = append(p.currMF.metrics[v].labels, p.currSeries...)
   314  			s = p.currMF.metrics[v].summary
   315  		}
   316  
   317  		p.summaries[hash] = s
   318  	}
   319  
   320  	switch {
   321  	case p.isQuantile:
   322  		s.quantiles = append(s.quantiles, Quantile{quantile: p.currQuantile, value: value})
   323  	case p.isSum:
   324  		s.sum = value
   325  	case p.isCount:
   326  		s.count = value
   327  	}
   328  }
   329  
   330  func (p *promTextParser) addHistogram(value float64) {
   331  	hash := p.currSeries.Hash()
   332  
   333  	p.currSeries = p.currSeries[1:] // remove "__name__"
   334  
   335  	h, ok := p.histograms[hash]
   336  	if !ok {
   337  		if v := len(p.currMF.metrics); v == cap(p.currMF.metrics) {
   338  			h = &Histogram{}
   339  			p.currMF.metrics = append(p.currMF.metrics, Metric{
   340  				labels:    copyLabels(p.currSeries),
   341  				histogram: h,
   342  			})
   343  		} else {
   344  			p.currMF.metrics = p.currMF.metrics[:v+1]
   345  			if p.currMF.metrics[v].histogram == nil {
   346  				p.currMF.metrics[v].histogram = &Histogram{}
   347  			}
   348  			p.currMF.metrics[v].histogram.sum = 0
   349  			p.currMF.metrics[v].histogram.count = 0
   350  			p.currMF.metrics[v].histogram.buckets = p.currMF.metrics[v].histogram.buckets[:0]
   351  			p.currMF.metrics[v].labels = p.currMF.metrics[v].labels[:0]
   352  			p.currMF.metrics[v].labels = append(p.currMF.metrics[v].labels, p.currSeries...)
   353  			h = p.currMF.metrics[v].histogram
   354  		}
   355  
   356  		p.histograms[hash] = h
   357  	}
   358  
   359  	switch {
   360  	case p.isBucket:
   361  		h.buckets = append(h.buckets, Bucket{upperBound: p.currBucket, cumulativeCount: value})
   362  	case p.isSum:
   363  		h.sum = value
   364  	case p.isCount:
   365  		h.count = value
   366  	}
   367  }
   368  
   369  func (p *promTextParser) reset() {
   370  	p.currMF = nil
   371  	p.currSeries = p.currSeries[:0]
   372  
   373  	if p.metrics == nil {
   374  		p.metrics = make(MetricFamilies)
   375  	}
   376  	for _, mf := range p.metrics {
   377  		mf.help = ""
   378  		mf.typ = ""
   379  		mf.metrics = mf.metrics[:0]
   380  	}
   381  
   382  	if p.summaries == nil {
   383  		p.summaries = make(map[uint64]*Summary)
   384  	}
   385  	for k := range p.summaries {
   386  		delete(p.summaries, k)
   387  	}
   388  
   389  	if p.histograms == nil {
   390  		p.histograms = make(map[uint64]*Histogram)
   391  	}
   392  	for k := range p.histograms {
   393  		delete(p.histograms, k)
   394  	}
   395  }
   396  
   397  func copyLabels(lbs []labels.Label) []labels.Label {
   398  	return append([]labels.Label(nil), lbs...)
   399  }
   400  
   401  func removeLabel(lbs labels.Labels, name string) (labels.Labels, string, bool) {
   402  	for i, v := range lbs {
   403  		if v.Name == name {
   404  			return append(lbs[:i], lbs[i+1:]...), v.Value, true
   405  		}
   406  	}
   407  	return lbs, "", false
   408  }
   409  
   410  func isSummaryOrHistogram(typ textparse.MetricType) bool {
   411  	return typ == textparse.MetricTypeSummary || typ == textparse.MetricTypeHistogram
   412  }