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 }