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 }