github.com/netdata/go.d.plugin@v0.58.1/modules/prometheus/collect.go (about)

     1  // SPDX-License-Identifier: GPL-3.0-or-later
     2  
     3  package prometheus
     4  
     5  import (
     6  	"fmt"
     7  	"math"
     8  	"strconv"
     9  	"strings"
    10  
    11  	"github.com/netdata/go.d.plugin/pkg/prometheus"
    12  
    13  	"github.com/prometheus/prometheus/model/labels"
    14  	"github.com/prometheus/prometheus/model/textparse"
    15  )
    16  
    17  const (
    18  	precision = 1000
    19  )
    20  
    21  func (p *Prometheus) collect() (map[string]int64, error) {
    22  	mfs, err := p.prom.Scrape()
    23  	if err != nil {
    24  		return nil, err
    25  	}
    26  
    27  	if mfs.Len() == 0 {
    28  		p.Warningf("endpoint '%s' returned 0 metric families", p.URL)
    29  		return nil, nil
    30  	}
    31  
    32  	if p.ExpectedPrefix != "" {
    33  		if !hasPrefix(mfs, p.ExpectedPrefix) {
    34  			return nil, fmt.Errorf("'%s' metrics have no expected prefix (%s)", p.URL, p.ExpectedPrefix)
    35  		}
    36  		p.ExpectedPrefix = ""
    37  	}
    38  
    39  	if p.MaxTS > 0 {
    40  		if n := calcMetrics(mfs); n > p.MaxTS {
    41  			return nil, fmt.Errorf("'%s' num of time series (%d) > limit (%d)", p.URL, n, p.MaxTS)
    42  		}
    43  		p.MaxTS = 0
    44  	}
    45  
    46  	mx := make(map[string]int64)
    47  
    48  	p.resetCache()
    49  	defer p.removeStaleCharts()
    50  
    51  	for _, mf := range mfs {
    52  		if strings.HasSuffix(mf.Name(), "_info") {
    53  			continue
    54  		}
    55  		if p.MaxTSPerMetric > 0 && len(mf.Metrics()) > p.MaxTSPerMetric {
    56  			p.Debugf("metric '%s' num of time series (%d) > limit (%d), skipping it",
    57  				mf.Name(), len(mf.Metrics()), p.MaxTSPerMetric)
    58  			continue
    59  		}
    60  
    61  		switch mf.Type() {
    62  		case textparse.MetricTypeGauge:
    63  			p.collectGauge(mx, mf)
    64  		case textparse.MetricTypeCounter:
    65  			p.collectCounter(mx, mf)
    66  		case textparse.MetricTypeSummary:
    67  			p.collectSummary(mx, mf)
    68  		case textparse.MetricTypeHistogram:
    69  			p.collectHistogram(mx, mf)
    70  		case textparse.MetricTypeUnknown:
    71  			p.collectUntyped(mx, mf)
    72  		}
    73  	}
    74  
    75  	return mx, nil
    76  }
    77  
    78  func (p *Prometheus) collectGauge(mx map[string]int64, mf *prometheus.MetricFamily) {
    79  	for _, m := range mf.Metrics() {
    80  		if m.Gauge() == nil || math.IsNaN(m.Gauge().Value()) {
    81  			continue
    82  		}
    83  
    84  		id := mf.Name() + p.joinLabels(m.Labels())
    85  
    86  		if !p.cache.hasP(id) {
    87  			p.addGaugeChart(id, mf.Name(), mf.Help(), m.Labels())
    88  		}
    89  
    90  		mx[id] = int64(m.Gauge().Value() * precision)
    91  	}
    92  }
    93  
    94  func (p *Prometheus) collectCounter(mx map[string]int64, mf *prometheus.MetricFamily) {
    95  	for _, m := range mf.Metrics() {
    96  		if m.Counter() == nil || math.IsNaN(m.Counter().Value()) {
    97  			continue
    98  		}
    99  
   100  		id := mf.Name() + p.joinLabels(m.Labels())
   101  
   102  		if !p.cache.hasP(id) {
   103  			p.addCounterChart(id, mf.Name(), mf.Help(), m.Labels())
   104  		}
   105  
   106  		mx[id] = int64(m.Counter().Value() * precision)
   107  	}
   108  }
   109  
   110  func (p *Prometheus) collectSummary(mx map[string]int64, mf *prometheus.MetricFamily) {
   111  	for _, m := range mf.Metrics() {
   112  		if m.Summary() == nil || len(m.Summary().Quantiles()) == 0 {
   113  			continue
   114  		}
   115  
   116  		id := mf.Name() + p.joinLabels(m.Labels())
   117  
   118  		if !p.cache.hasP(id) {
   119  			p.addSummaryCharts(id, mf.Name(), mf.Help(), m.Labels(), m.Summary().Quantiles())
   120  		}
   121  
   122  		for _, v := range m.Summary().Quantiles() {
   123  			if !math.IsNaN(v.Value()) {
   124  				dimID := fmt.Sprintf("%s_quantile=%s", id, formatFloat(v.Quantile()))
   125  				mx[dimID] = int64(v.Value() * precision * precision)
   126  			}
   127  		}
   128  
   129  		mx[id+"_sum"] = int64(m.Summary().Sum() * precision)
   130  		mx[id+"_count"] = int64(m.Summary().Count())
   131  	}
   132  }
   133  
   134  func (p *Prometheus) collectHistogram(mx map[string]int64, mf *prometheus.MetricFamily) {
   135  	for _, m := range mf.Metrics() {
   136  		if m.Histogram() == nil || len(m.Histogram().Buckets()) == 0 {
   137  			continue
   138  		}
   139  
   140  		id := mf.Name() + p.joinLabels(m.Labels())
   141  
   142  		if !p.cache.hasP(id) {
   143  			p.addHistogramCharts(id, mf.Name(), mf.Help(), m.Labels(), m.Histogram().Buckets())
   144  		}
   145  
   146  		for _, v := range m.Histogram().Buckets() {
   147  			if !math.IsNaN(v.CumulativeCount()) {
   148  				dimID := fmt.Sprintf("%s_bucket=%s", id, formatFloat(v.UpperBound()))
   149  				mx[dimID] = int64(v.CumulativeCount())
   150  			}
   151  		}
   152  
   153  		mx[id+"_sum"] = int64(m.Histogram().Sum() * precision)
   154  		mx[id+"_count"] = int64(m.Histogram().Count())
   155  	}
   156  }
   157  
   158  func (p *Prometheus) collectUntyped(mx map[string]int64, mf *prometheus.MetricFamily) {
   159  	for _, m := range mf.Metrics() {
   160  		if m.Untyped() == nil || math.IsNaN(m.Untyped().Value()) {
   161  			continue
   162  		}
   163  
   164  		if p.isFallbackTypeGauge(mf.Name()) {
   165  			id := mf.Name() + p.joinLabels(m.Labels())
   166  
   167  			if !p.cache.hasP(id) {
   168  				p.addGaugeChart(id, mf.Name(), mf.Help(), m.Labels())
   169  			}
   170  
   171  			mx[id] = int64(m.Untyped().Value() * precision)
   172  		}
   173  
   174  		if p.isFallbackTypeCounter(mf.Name()) || strings.HasSuffix(mf.Name(), "_total") {
   175  			id := mf.Name() + p.joinLabels(m.Labels())
   176  
   177  			if !p.cache.hasP(id) {
   178  				p.addCounterChart(id, mf.Name(), mf.Help(), m.Labels())
   179  			}
   180  
   181  			mx[id] = int64(m.Untyped().Value() * precision)
   182  		}
   183  	}
   184  }
   185  
   186  func (p *Prometheus) isFallbackTypeGauge(name string) bool {
   187  	return p.fallbackType.gauge != nil && p.fallbackType.gauge.MatchString(name)
   188  }
   189  
   190  func (p *Prometheus) isFallbackTypeCounter(name string) bool {
   191  	return p.fallbackType.counter != nil && p.fallbackType.counter.MatchString(name)
   192  }
   193  
   194  func (p *Prometheus) joinLabels(labels labels.Labels) string {
   195  	var sb strings.Builder
   196  	for _, lbl := range labels {
   197  		name, val := lbl.Name, lbl.Value
   198  		if name == "" || val == "" {
   199  			continue
   200  		}
   201  
   202  		if strings.IndexByte(val, ' ') != -1 {
   203  			val = spaceReplacer.Replace(val)
   204  		}
   205  		if strings.IndexByte(val, '\\') != -1 {
   206  			if val = decodeLabelValue(val); strings.IndexByte(val, '\\') != -1 {
   207  				val = backslashReplacer.Replace(val)
   208  			}
   209  		}
   210  
   211  		sb.WriteString("-" + name + "=" + val)
   212  	}
   213  	return sb.String()
   214  }
   215  
   216  func (p *Prometheus) resetCache() {
   217  	for _, v := range p.cache.entries {
   218  		v.seen = false
   219  	}
   220  }
   221  
   222  const maxNotSeenTimes = 10
   223  
   224  func (p *Prometheus) removeStaleCharts() {
   225  	for k, v := range p.cache.entries {
   226  		if v.seen {
   227  			continue
   228  		}
   229  		if v.notSeenTimes++; v.notSeenTimes >= maxNotSeenTimes {
   230  			for _, chart := range v.charts {
   231  				chart.MarkRemove()
   232  				chart.MarkNotCreated()
   233  			}
   234  			delete(p.cache.entries, k)
   235  		}
   236  	}
   237  }
   238  
   239  func decodeLabelValue(value string) string {
   240  	v, err := strconv.Unquote("\"" + value + "\"")
   241  	if err != nil {
   242  		return value
   243  	}
   244  	return v
   245  }
   246  
   247  var (
   248  	spaceReplacer     = strings.NewReplacer(" ", "_")
   249  	backslashReplacer = strings.NewReplacer(`\`, "_")
   250  )
   251  
   252  func hasPrefix(mf map[string]*prometheus.MetricFamily, prefix string) bool {
   253  	for name := range mf {
   254  		if strings.HasPrefix(name, prefix) {
   255  			return true
   256  		}
   257  	}
   258  	return false
   259  }
   260  
   261  func calcMetrics(mfs prometheus.MetricFamilies) int {
   262  	var n int
   263  	for _, mf := range mfs {
   264  		n += len(mf.Metrics())
   265  	}
   266  	return n
   267  }
   268  
   269  func formatFloat(v float64) string {
   270  	return strconv.FormatFloat(v, 'f', -1, 64)
   271  }