mirror of
https://github.com/prometheus/prometheus.git
synced 2025-01-12 06:17:27 -08:00
Add exemplar support to the openmetrics parser (#6292)
* Add exemplar support to the openmetrics parser Signed-off-by: Shreyas Srivatsan <shreyas@chronosphere.io>
This commit is contained in:
parent
ad4bc5701e
commit
e825282dd1
24
pkg/exemplar/exemplar.go
Normal file
24
pkg/exemplar/exemplar.go
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
// Copyright 2019 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package exemplar
|
||||||
|
|
||||||
|
import "github.com/prometheus/prometheus/pkg/labels"
|
||||||
|
|
||||||
|
// Exemplar is additional information associated with a time series.
|
||||||
|
type Exemplar struct {
|
||||||
|
Labels labels.Labels
|
||||||
|
Value float64
|
||||||
|
HasTs bool
|
||||||
|
Ts int64
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ package textparse
|
||||||
import (
|
import (
|
||||||
"mime"
|
"mime"
|
||||||
|
|
||||||
|
"github.com/prometheus/prometheus/pkg/exemplar"
|
||||||
"github.com/prometheus/prometheus/pkg/labels"
|
"github.com/prometheus/prometheus/pkg/labels"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -50,6 +51,10 @@ type Parser interface {
|
||||||
// It returns the string from which the metric was parsed.
|
// It returns the string from which the metric was parsed.
|
||||||
Metric(l *labels.Labels) string
|
Metric(l *labels.Labels) string
|
||||||
|
|
||||||
|
// Exemplar writes the exemplar of the current sample into the passed
|
||||||
|
// exemplar. It returns if an exemplar exists or not.
|
||||||
|
Exemplar(l *exemplar.Exemplar) bool
|
||||||
|
|
||||||
// Next advances the parser to the next sample. It returns false if no
|
// Next advances the parser to the next sample. It returns false if no
|
||||||
// more samples were read or an error occurred.
|
// more samples were read or an error occurred.
|
||||||
Next() (Entry, error)
|
Next() (Entry, error)
|
||||||
|
|
|
@ -36,7 +36,7 @@ M [a-zA-Z_:]
|
||||||
C [^\n]
|
C [^\n]
|
||||||
S [ ]
|
S [ ]
|
||||||
|
|
||||||
%x sComment sMeta1 sMeta2 sLabels sLValue sValue sTimestamp
|
%x sComment sMeta1 sMeta2 sLabels sLValue sValue sTimestamp sExemplar sEValue sETimestamp
|
||||||
|
|
||||||
%yyc c
|
%yyc c
|
||||||
%yyn c = l.next()
|
%yyn c = l.next()
|
||||||
|
@ -62,8 +62,17 @@ S [ ]
|
||||||
<sLValue>\"(\\.|[^\\"\n])*\" l.state = sLabels; return tLValue
|
<sLValue>\"(\\.|[^\\"\n])*\" l.state = sLabels; return tLValue
|
||||||
<sValue>{S}[^ \n]+ l.state = sTimestamp; return tValue
|
<sValue>{S}[^ \n]+ l.state = sTimestamp; return tValue
|
||||||
<sTimestamp>{S}[^ \n]+ return tTimestamp
|
<sTimestamp>{S}[^ \n]+ return tTimestamp
|
||||||
<sTimestamp>{S}#{S}{C}*\n l.state = sInit; return tLinebreak
|
|
||||||
<sTimestamp>\n l.state = sInit; return tLinebreak
|
<sTimestamp>\n l.state = sInit; return tLinebreak
|
||||||
|
<sTimestamp>{S}#{S}\{ l.state = sExemplar; return tComment
|
||||||
|
|
||||||
|
<sExemplar>{L}({L}|{D})* return tLName
|
||||||
|
<sExemplar>\} l.state = sEValue; return tBraceClose
|
||||||
|
<sExemplar>= l.state = sEValue; return tEqual
|
||||||
|
<sEValue>\"(\\.|[^\\"\n])*\" l.state = sExemplar; return tLValue
|
||||||
|
<sExemplar>, return tComma
|
||||||
|
<sEValue>{S}[^ \n]+ l.state = sETimestamp; return tValue
|
||||||
|
<sETimestamp>{S}[^ \n]+ return tTimestamp
|
||||||
|
<sETimestamp>\n l.state = sInit; return tLinebreak
|
||||||
|
|
||||||
%%
|
%%
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// CAUTION: Generated file - DO NOT EDIT.
|
// Code generated by golex. DO NOT EDIT.
|
||||||
|
|
||||||
// Copyright 2018 The Prometheus Authors
|
// Copyright 2018 The Prometheus Authors
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@ -16,7 +16,7 @@
|
||||||
package textparse
|
package textparse
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/pkg/errors"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Lex is called by the parser generated by "go tool yacc" to obtain each
|
// Lex is called by the parser generated by "go tool yacc" to obtain each
|
||||||
|
@ -33,7 +33,7 @@ yystate0:
|
||||||
|
|
||||||
switch yyt := l.state; yyt {
|
switch yyt := l.state; yyt {
|
||||||
default:
|
default:
|
||||||
panic(errors.Errorf(`invalid start condition %d`, yyt))
|
panic(fmt.Errorf(`invalid start condition %d`, yyt))
|
||||||
case 0: // start condition: INITIAL
|
case 0: // start condition: INITIAL
|
||||||
goto yystart1
|
goto yystart1
|
||||||
case 1: // start condition: sComment
|
case 1: // start condition: sComment
|
||||||
|
@ -50,6 +50,12 @@ yystate0:
|
||||||
goto yystart39
|
goto yystart39
|
||||||
case 7: // start condition: sTimestamp
|
case 7: // start condition: sTimestamp
|
||||||
goto yystart43
|
goto yystart43
|
||||||
|
case 8: // start condition: sExemplar
|
||||||
|
goto yystart50
|
||||||
|
case 9: // start condition: sEValue
|
||||||
|
goto yystart55
|
||||||
|
case 10: // start condition: sETimestamp
|
||||||
|
goto yystart61
|
||||||
}
|
}
|
||||||
|
|
||||||
goto yystate0 // silence unused label error
|
goto yystate0 // silence unused label error
|
||||||
|
@ -427,7 +433,7 @@ yystart43:
|
||||||
|
|
||||||
yystate44:
|
yystate44:
|
||||||
c = l.next()
|
c = l.next()
|
||||||
goto yyrule18
|
goto yyrule17
|
||||||
|
|
||||||
yystate45:
|
yystate45:
|
||||||
c = l.next()
|
c = l.next()
|
||||||
|
@ -465,15 +471,143 @@ yystate48:
|
||||||
switch {
|
switch {
|
||||||
default:
|
default:
|
||||||
goto yyabort
|
goto yyabort
|
||||||
case c == '\n':
|
case c == '{':
|
||||||
goto yystate49
|
goto yystate49
|
||||||
case c >= '\x01' && c <= '\t' || c >= '\v' && c <= 'ÿ':
|
|
||||||
goto yystate48
|
|
||||||
}
|
}
|
||||||
|
|
||||||
yystate49:
|
yystate49:
|
||||||
c = l.next()
|
c = l.next()
|
||||||
goto yyrule17
|
goto yyrule18
|
||||||
|
|
||||||
|
goto yystate50 // silence unused label error
|
||||||
|
yystate50:
|
||||||
|
c = l.next()
|
||||||
|
yystart50:
|
||||||
|
switch {
|
||||||
|
default:
|
||||||
|
goto yyabort
|
||||||
|
case c == ',':
|
||||||
|
goto yystate51
|
||||||
|
case c == '=':
|
||||||
|
goto yystate52
|
||||||
|
case c == '}':
|
||||||
|
goto yystate54
|
||||||
|
case c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
|
||||||
|
goto yystate53
|
||||||
|
}
|
||||||
|
|
||||||
|
yystate51:
|
||||||
|
c = l.next()
|
||||||
|
goto yyrule23
|
||||||
|
|
||||||
|
yystate52:
|
||||||
|
c = l.next()
|
||||||
|
goto yyrule21
|
||||||
|
|
||||||
|
yystate53:
|
||||||
|
c = l.next()
|
||||||
|
switch {
|
||||||
|
default:
|
||||||
|
goto yyrule19
|
||||||
|
case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
|
||||||
|
goto yystate53
|
||||||
|
}
|
||||||
|
|
||||||
|
yystate54:
|
||||||
|
c = l.next()
|
||||||
|
goto yyrule20
|
||||||
|
|
||||||
|
goto yystate55 // silence unused label error
|
||||||
|
yystate55:
|
||||||
|
c = l.next()
|
||||||
|
yystart55:
|
||||||
|
switch {
|
||||||
|
default:
|
||||||
|
goto yyabort
|
||||||
|
case c == ' ':
|
||||||
|
goto yystate56
|
||||||
|
case c == '"':
|
||||||
|
goto yystate58
|
||||||
|
}
|
||||||
|
|
||||||
|
yystate56:
|
||||||
|
c = l.next()
|
||||||
|
switch {
|
||||||
|
default:
|
||||||
|
goto yyabort
|
||||||
|
case c >= '\x01' && c <= '\t' || c >= '\v' && c <= '\x1f' || c >= '!' && c <= 'ÿ':
|
||||||
|
goto yystate57
|
||||||
|
}
|
||||||
|
|
||||||
|
yystate57:
|
||||||
|
c = l.next()
|
||||||
|
switch {
|
||||||
|
default:
|
||||||
|
goto yyrule24
|
||||||
|
case c >= '\x01' && c <= '\t' || c >= '\v' && c <= '\x1f' || c >= '!' && c <= 'ÿ':
|
||||||
|
goto yystate57
|
||||||
|
}
|
||||||
|
|
||||||
|
yystate58:
|
||||||
|
c = l.next()
|
||||||
|
switch {
|
||||||
|
default:
|
||||||
|
goto yyabort
|
||||||
|
case c == '"':
|
||||||
|
goto yystate59
|
||||||
|
case c == '\\':
|
||||||
|
goto yystate60
|
||||||
|
case c >= '\x01' && c <= '\t' || c >= '\v' && c <= '!' || c >= '#' && c <= '[' || c >= ']' && c <= 'ÿ':
|
||||||
|
goto yystate58
|
||||||
|
}
|
||||||
|
|
||||||
|
yystate59:
|
||||||
|
c = l.next()
|
||||||
|
goto yyrule22
|
||||||
|
|
||||||
|
yystate60:
|
||||||
|
c = l.next()
|
||||||
|
switch {
|
||||||
|
default:
|
||||||
|
goto yyabort
|
||||||
|
case c >= '\x01' && c <= '\t' || c >= '\v' && c <= 'ÿ':
|
||||||
|
goto yystate58
|
||||||
|
}
|
||||||
|
|
||||||
|
goto yystate61 // silence unused label error
|
||||||
|
yystate61:
|
||||||
|
c = l.next()
|
||||||
|
yystart61:
|
||||||
|
switch {
|
||||||
|
default:
|
||||||
|
goto yyabort
|
||||||
|
case c == ' ':
|
||||||
|
goto yystate63
|
||||||
|
case c == '\n':
|
||||||
|
goto yystate62
|
||||||
|
}
|
||||||
|
|
||||||
|
yystate62:
|
||||||
|
c = l.next()
|
||||||
|
goto yyrule26
|
||||||
|
|
||||||
|
yystate63:
|
||||||
|
c = l.next()
|
||||||
|
switch {
|
||||||
|
default:
|
||||||
|
goto yyabort
|
||||||
|
case c >= '\x01' && c <= '\t' || c >= '\v' && c <= '\x1f' || c >= '!' && c <= 'ÿ':
|
||||||
|
goto yystate64
|
||||||
|
}
|
||||||
|
|
||||||
|
yystate64:
|
||||||
|
c = l.next()
|
||||||
|
switch {
|
||||||
|
default:
|
||||||
|
goto yyrule25
|
||||||
|
case c >= '\x01' && c <= '\t' || c >= '\v' && c <= '\x1f' || c >= '!' && c <= 'ÿ':
|
||||||
|
goto yystate64
|
||||||
|
}
|
||||||
|
|
||||||
yyrule1: // #{S}
|
yyrule1: // #{S}
|
||||||
{
|
{
|
||||||
|
@ -564,13 +698,55 @@ yyrule16: // {S}[^ \n]+
|
||||||
{
|
{
|
||||||
return tTimestamp
|
return tTimestamp
|
||||||
}
|
}
|
||||||
yyrule17: // {S}#{S}{C}*\n
|
yyrule17: // \n
|
||||||
{
|
{
|
||||||
l.state = sInit
|
l.state = sInit
|
||||||
return tLinebreak
|
return tLinebreak
|
||||||
goto yystate0
|
goto yystate0
|
||||||
}
|
}
|
||||||
yyrule18: // \n
|
yyrule18: // {S}#{S}\{
|
||||||
|
{
|
||||||
|
l.state = sExemplar
|
||||||
|
return tComment
|
||||||
|
goto yystate0
|
||||||
|
}
|
||||||
|
yyrule19: // {L}({L}|{D})*
|
||||||
|
{
|
||||||
|
return tLName
|
||||||
|
}
|
||||||
|
yyrule20: // \}
|
||||||
|
{
|
||||||
|
l.state = sEValue
|
||||||
|
return tBraceClose
|
||||||
|
goto yystate0
|
||||||
|
}
|
||||||
|
yyrule21: // =
|
||||||
|
{
|
||||||
|
l.state = sEValue
|
||||||
|
return tEqual
|
||||||
|
goto yystate0
|
||||||
|
}
|
||||||
|
yyrule22: // \"(\\.|[^\\"\n])*\"
|
||||||
|
{
|
||||||
|
l.state = sExemplar
|
||||||
|
return tLValue
|
||||||
|
goto yystate0
|
||||||
|
}
|
||||||
|
yyrule23: // ,
|
||||||
|
{
|
||||||
|
return tComma
|
||||||
|
}
|
||||||
|
yyrule24: // {S}[^ \n]+
|
||||||
|
{
|
||||||
|
l.state = sETimestamp
|
||||||
|
return tValue
|
||||||
|
goto yystate0
|
||||||
|
}
|
||||||
|
yyrule25: // {S}[^ \n]+
|
||||||
|
{
|
||||||
|
return tTimestamp
|
||||||
|
}
|
||||||
|
yyrule26: // \n
|
||||||
{
|
{
|
||||||
l.state = sInit
|
l.state = sInit
|
||||||
return tLinebreak
|
return tLinebreak
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
package textparse
|
package textparse
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"sort"
|
"sort"
|
||||||
|
@ -25,10 +27,13 @@ import (
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/prometheus/prometheus/pkg/exemplar"
|
||||||
"github.com/prometheus/prometheus/pkg/labels"
|
"github.com/prometheus/prometheus/pkg/labels"
|
||||||
"github.com/prometheus/prometheus/pkg/value"
|
"github.com/prometheus/prometheus/pkg/value"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var allowedSuffixes = [][]byte{[]byte("_total"), []byte("_bucket")}
|
||||||
|
|
||||||
type openMetricsLexer struct {
|
type openMetricsLexer struct {
|
||||||
b []byte
|
b []byte
|
||||||
i int
|
i int
|
||||||
|
@ -85,6 +90,12 @@ type OpenMetricsParser struct {
|
||||||
hasTS bool
|
hasTS bool
|
||||||
start int
|
start int
|
||||||
offsets []int
|
offsets []int
|
||||||
|
|
||||||
|
eOffsets []int
|
||||||
|
exemplar []byte
|
||||||
|
exemplarVal float64
|
||||||
|
exemplarTs int64
|
||||||
|
hasExemplarTs bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewOpenMetricsParser returns a new parser of the byte slice.
|
// NewOpenMetricsParser returns a new parser of the byte slice.
|
||||||
|
@ -96,7 +107,8 @@ func NewOpenMetricsParser(b []byte) Parser {
|
||||||
// of the current sample.
|
// of the current sample.
|
||||||
func (p *OpenMetricsParser) Series() ([]byte, *int64, float64) {
|
func (p *OpenMetricsParser) Series() ([]byte, *int64, float64) {
|
||||||
if p.hasTS {
|
if p.hasTS {
|
||||||
return p.series, &p.ts, p.val
|
ts := p.ts
|
||||||
|
return p.series, &ts, p.val
|
||||||
}
|
}
|
||||||
return p.series, nil, p.val
|
return p.series, nil, p.val
|
||||||
}
|
}
|
||||||
|
@ -170,6 +182,38 @@ func (p *OpenMetricsParser) Metric(l *labels.Labels) string {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Exemplar writes the exemplar of the current sample into the passed
|
||||||
|
// exemplar. It returns the whether an exemplar exists.
|
||||||
|
func (p *OpenMetricsParser) Exemplar(e *exemplar.Exemplar) bool {
|
||||||
|
if len(p.exemplar) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate the full immutable string immediately, so we just
|
||||||
|
// have to create references on it below.
|
||||||
|
s := string(p.exemplar)
|
||||||
|
|
||||||
|
e.Value = p.exemplarVal
|
||||||
|
if p.hasExemplarTs {
|
||||||
|
e.HasTs = true
|
||||||
|
e.Ts = p.exemplarTs
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(p.eOffsets); i += 4 {
|
||||||
|
a := p.eOffsets[i] - p.start
|
||||||
|
b := p.eOffsets[i+1] - p.start
|
||||||
|
c := p.eOffsets[i+2] - p.start
|
||||||
|
d := p.eOffsets[i+3] - p.start
|
||||||
|
|
||||||
|
e.Labels = append(e.Labels, labels.Label{Name: s[a:b], Value: s[c:d]})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort the labels.
|
||||||
|
sort.Sort(e.Labels)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// nextToken returns the next token from the openMetricsLexer.
|
// nextToken returns the next token from the openMetricsLexer.
|
||||||
func (p *OpenMetricsParser) nextToken() token {
|
func (p *OpenMetricsParser) nextToken() token {
|
||||||
tok := p.l.Lex()
|
tok := p.l.Lex()
|
||||||
|
@ -183,6 +227,10 @@ func (p *OpenMetricsParser) Next() (Entry, error) {
|
||||||
|
|
||||||
p.start = p.l.i
|
p.start = p.l.i
|
||||||
p.offsets = p.offsets[:0]
|
p.offsets = p.offsets[:0]
|
||||||
|
p.eOffsets = p.eOffsets[:0]
|
||||||
|
p.exemplar = p.exemplar[:0]
|
||||||
|
p.exemplarVal = 0
|
||||||
|
p.hasExemplarTs = false
|
||||||
|
|
||||||
switch t := p.nextToken(); t {
|
switch t := p.nextToken(); t {
|
||||||
case tEofWord:
|
case tEofWord:
|
||||||
|
@ -191,7 +239,7 @@ func (p *OpenMetricsParser) Next() (Entry, error) {
|
||||||
}
|
}
|
||||||
return EntryInvalid, io.EOF
|
return EntryInvalid, io.EOF
|
||||||
case tEOF:
|
case tEOF:
|
||||||
return EntryInvalid, parseError("unexpected end of data", t)
|
return EntryInvalid, io.EOF
|
||||||
case tHelp, tType, tUnit:
|
case tHelp, tType, tUnit:
|
||||||
switch t := p.nextToken(); t {
|
switch t := p.nextToken(); t {
|
||||||
case tMName:
|
case tMName:
|
||||||
|
@ -258,26 +306,29 @@ func (p *OpenMetricsParser) Next() (Entry, error) {
|
||||||
|
|
||||||
t2 := p.nextToken()
|
t2 := p.nextToken()
|
||||||
if t2 == tBraceOpen {
|
if t2 == tBraceOpen {
|
||||||
if err := p.parseLVals(); err != nil {
|
offsets, err := p.parseLVals()
|
||||||
|
if err != nil {
|
||||||
return EntryInvalid, err
|
return EntryInvalid, err
|
||||||
}
|
}
|
||||||
|
p.offsets = append(p.offsets, offsets...)
|
||||||
p.series = p.l.b[p.start:p.l.i]
|
p.series = p.l.b[p.start:p.l.i]
|
||||||
t2 = p.nextToken()
|
t2 = p.nextToken()
|
||||||
}
|
}
|
||||||
if t2 != tValue {
|
p.val, err = p.getFloatValue(t2, "metric")
|
||||||
return EntryInvalid, parseError("expected value after metric", t)
|
if err != nil {
|
||||||
}
|
|
||||||
if p.val, err = parseFloat(yoloString(p.l.buf()[1:])); err != nil {
|
|
||||||
return EntryInvalid, err
|
return EntryInvalid, err
|
||||||
}
|
}
|
||||||
// Ensure canonical NaN value.
|
|
||||||
if math.IsNaN(p.val) {
|
|
||||||
p.val = math.Float64frombits(value.NormalNaN)
|
|
||||||
}
|
|
||||||
p.hasTS = false
|
p.hasTS = false
|
||||||
switch p.nextToken() {
|
switch t2 := p.nextToken(); t2 {
|
||||||
|
case tEOF:
|
||||||
|
return EntryInvalid, io.EOF
|
||||||
case tLinebreak:
|
case tLinebreak:
|
||||||
break
|
break
|
||||||
|
case tComment:
|
||||||
|
if err := p.parseComment(); err != nil {
|
||||||
|
return EntryInvalid, err
|
||||||
|
}
|
||||||
case tTimestamp:
|
case tTimestamp:
|
||||||
p.hasTS = true
|
p.hasTS = true
|
||||||
var ts float64
|
var ts float64
|
||||||
|
@ -286,11 +337,17 @@ func (p *OpenMetricsParser) Next() (Entry, error) {
|
||||||
return EntryInvalid, err
|
return EntryInvalid, err
|
||||||
}
|
}
|
||||||
p.ts = int64(ts * 1000)
|
p.ts = int64(ts * 1000)
|
||||||
if t2 := p.nextToken(); t2 != tLinebreak {
|
switch t3 := p.nextToken(); t3 {
|
||||||
return EntryInvalid, parseError("expected next entry after timestamp", t)
|
case tLinebreak:
|
||||||
|
case tComment:
|
||||||
|
if err := p.parseComment(); err != nil {
|
||||||
|
return EntryInvalid, err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return EntryInvalid, parseError("expected next entry after timestamp", t3)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return EntryInvalid, parseError("expected timestamp or new record", t)
|
return EntryInvalid, parseError("expected timestamp or # symbol", t2)
|
||||||
}
|
}
|
||||||
return EntrySeries, nil
|
return EntrySeries, nil
|
||||||
|
|
||||||
|
@ -300,50 +357,121 @@ func (p *OpenMetricsParser) Next() (Entry, error) {
|
||||||
return EntryInvalid, err
|
return EntryInvalid, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *OpenMetricsParser) parseLVals() error {
|
func (p *OpenMetricsParser) parseComment() error {
|
||||||
|
// Validate the name of the metric. It must have _total or _bucket as
|
||||||
|
// suffix for exemplars to be supported.
|
||||||
|
if err := p.validateNameForExemplar(p.series[:p.offsets[0]-p.start]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the labels.
|
||||||
|
offsets, err := p.parseLVals()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.eOffsets = append(p.eOffsets, offsets...)
|
||||||
|
p.exemplar = p.l.b[p.start:p.l.i]
|
||||||
|
|
||||||
|
// Get the value.
|
||||||
|
p.exemplarVal, err = p.getFloatValue(p.nextToken(), "exemplar labels")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the optional timestamp.
|
||||||
|
p.hasExemplarTs = false
|
||||||
|
switch t2 := p.nextToken(); t2 {
|
||||||
|
case tEOF:
|
||||||
|
return io.EOF
|
||||||
|
case tLinebreak:
|
||||||
|
break
|
||||||
|
case tTimestamp:
|
||||||
|
p.hasExemplarTs = true
|
||||||
|
var ts float64
|
||||||
|
// A float is enough to hold what we need for millisecond resolution.
|
||||||
|
if ts, err = parseFloat(yoloString(p.l.buf()[1:])); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.exemplarTs = int64(ts * 1000)
|
||||||
|
switch t3 := p.nextToken(); t3 {
|
||||||
|
case tLinebreak:
|
||||||
|
default:
|
||||||
|
return parseError("expected next entry after exemplar timestamp", t3)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return parseError("expected timestamp or comment", t2)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *OpenMetricsParser) parseLVals() ([]int, error) {
|
||||||
|
var offsets []int
|
||||||
first := true
|
first := true
|
||||||
for {
|
for {
|
||||||
t := p.nextToken()
|
t := p.nextToken()
|
||||||
switch t {
|
switch t {
|
||||||
case tBraceClose:
|
case tBraceClose:
|
||||||
return nil
|
return offsets, nil
|
||||||
case tComma:
|
case tComma:
|
||||||
if first {
|
if first {
|
||||||
return parseError("expected label name or left brace", t)
|
return nil, parseError("expected label name or left brace", t)
|
||||||
}
|
}
|
||||||
t = p.nextToken()
|
t = p.nextToken()
|
||||||
if t != tLName {
|
if t != tLName {
|
||||||
return parseError("expected label name", t)
|
return nil, parseError("expected label name", t)
|
||||||
}
|
}
|
||||||
case tLName:
|
case tLName:
|
||||||
if !first {
|
if !first {
|
||||||
return parseError("expected comma", t)
|
return nil, parseError("expected comma", t)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
if first {
|
if first {
|
||||||
return parseError("expected label name or left brace", t)
|
return nil, parseError("expected label name or left brace", t)
|
||||||
}
|
}
|
||||||
return parseError("expected comma or left brace", t)
|
return nil, parseError("expected comma or left brace", t)
|
||||||
|
|
||||||
}
|
}
|
||||||
first = false
|
first = false
|
||||||
// t is now a label name.
|
// t is now a label name.
|
||||||
|
|
||||||
p.offsets = append(p.offsets, p.l.start, p.l.i)
|
offsets = append(offsets, p.l.start, p.l.i)
|
||||||
|
|
||||||
if t := p.nextToken(); t != tEqual {
|
if t := p.nextToken(); t != tEqual {
|
||||||
return parseError("expected equal", t)
|
return nil, parseError("expected equal", t)
|
||||||
}
|
}
|
||||||
if t := p.nextToken(); t != tLValue {
|
if t := p.nextToken(); t != tLValue {
|
||||||
return parseError("expected label value", t)
|
return nil, parseError("expected label value", t)
|
||||||
}
|
}
|
||||||
if !utf8.Valid(p.l.buf()) {
|
if !utf8.Valid(p.l.buf()) {
|
||||||
return errors.New("invalid UTF-8 label value")
|
return nil, errors.New("invalid UTF-8 label value")
|
||||||
}
|
}
|
||||||
|
|
||||||
// The openMetricsLexer ensures the value string is quoted. Strip first
|
// The openMetricsLexer ensures the value string is quoted. Strip first
|
||||||
// and last character.
|
// and last character.
|
||||||
p.offsets = append(p.offsets, p.l.start+1, p.l.i-1)
|
offsets = append(offsets, p.l.start+1, p.l.i-1)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *OpenMetricsParser) getFloatValue(t token, after string) (float64, error) {
|
||||||
|
if t != tValue {
|
||||||
|
return 0, parseError(fmt.Sprintf("expected value after %v", after), t)
|
||||||
|
}
|
||||||
|
val, err := parseFloat(yoloString(p.l.buf()[1:]))
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
// Ensure canonical NaN value.
|
||||||
|
if math.IsNaN(p.exemplarVal) {
|
||||||
|
val = math.Float64frombits(value.NormalNaN)
|
||||||
|
}
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *OpenMetricsParser) validateNameForExemplar(name []byte) error {
|
||||||
|
for _, suffix := range allowedSuffixes {
|
||||||
|
if bytes.HasSuffix(name, suffix) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("metric name %v does not support exemplars", string(name))
|
||||||
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/prometheus/prometheus/pkg/exemplar"
|
||||||
"github.com/prometheus/prometheus/pkg/labels"
|
"github.com/prometheus/prometheus/pkg/labels"
|
||||||
"github.com/prometheus/prometheus/util/testutil"
|
"github.com/prometheus/prometheus/util/testutil"
|
||||||
)
|
)
|
||||||
|
@ -38,9 +39,13 @@ some:aggregate:rate5m{a_b="c"} 1
|
||||||
# TYPE go_goroutines gauge
|
# TYPE go_goroutines gauge
|
||||||
go_goroutines 33 123.123
|
go_goroutines 33 123.123
|
||||||
# TYPE hh histogram
|
# TYPE hh histogram
|
||||||
hh_bucket{le="+Inf"} 1 # {} 4
|
hh_bucket{le="+Inf"} 1
|
||||||
# TYPE gh gaugehistogram
|
# TYPE gh gaugehistogram
|
||||||
gh_bucket{le="+Inf"} 1 # {} 4
|
gh_bucket{le="+Inf"} 1
|
||||||
|
# TYPE hhh histogram
|
||||||
|
hhh_bucket{le="+Inf"} 1 # {aa="bb"} 4
|
||||||
|
# TYPE ggh gaugehistogram
|
||||||
|
ggh_bucket{le="+Inf"} 1 # {cc="dd",xx="yy"} 4 123.123
|
||||||
# TYPE ii info
|
# TYPE ii info
|
||||||
ii{foo="bar"} 1
|
ii{foo="bar"} 1
|
||||||
# TYPE ss stateset
|
# TYPE ss stateset
|
||||||
|
@ -49,7 +54,9 @@ ss{ss="bar"} 0
|
||||||
# TYPE un unknown
|
# TYPE un unknown
|
||||||
_metric_starting_with_underscore 1
|
_metric_starting_with_underscore 1
|
||||||
testmetric{_label_starting_with_underscore="foo"} 1
|
testmetric{_label_starting_with_underscore="foo"} 1
|
||||||
testmetric{label="\"bar\""} 1`
|
testmetric{label="\"bar\""} 1
|
||||||
|
# TYPE foo counter
|
||||||
|
foo_total 17.0 1520879607.789 # {xx="yy"} 5`
|
||||||
|
|
||||||
input += "\n# HELP metric foo\x00bar"
|
input += "\n# HELP metric foo\x00bar"
|
||||||
input += "\nnull_byte_metric{a=\"abc\x00\"} 1"
|
input += "\nnull_byte_metric{a=\"abc\x00\"} 1"
|
||||||
|
@ -66,6 +73,7 @@ testmetric{label="\"bar\""} 1`
|
||||||
help string
|
help string
|
||||||
unit string
|
unit string
|
||||||
comment string
|
comment string
|
||||||
|
e *exemplar.Exemplar
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
m: "go_gc_duration_seconds",
|
m: "go_gc_duration_seconds",
|
||||||
|
@ -134,6 +142,22 @@ testmetric{label="\"bar\""} 1`
|
||||||
m: `gh_bucket{le="+Inf"}`,
|
m: `gh_bucket{le="+Inf"}`,
|
||||||
v: 1,
|
v: 1,
|
||||||
lset: labels.FromStrings("__name__", "gh_bucket", "le", "+Inf"),
|
lset: labels.FromStrings("__name__", "gh_bucket", "le", "+Inf"),
|
||||||
|
}, {
|
||||||
|
m: "hhh",
|
||||||
|
typ: MetricTypeHistogram,
|
||||||
|
}, {
|
||||||
|
m: `hhh_bucket{le="+Inf"}`,
|
||||||
|
v: 1,
|
||||||
|
lset: labels.FromStrings("__name__", "hhh_bucket", "le", "+Inf"),
|
||||||
|
e: &exemplar.Exemplar{Labels: labels.FromStrings("aa", "bb"), Value: 4},
|
||||||
|
}, {
|
||||||
|
m: "ggh",
|
||||||
|
typ: MetricTypeGaugeHistogram,
|
||||||
|
}, {
|
||||||
|
m: `ggh_bucket{le="+Inf"}`,
|
||||||
|
v: 1,
|
||||||
|
lset: labels.FromStrings("__name__", "ggh_bucket", "le", "+Inf"),
|
||||||
|
e: &exemplar.Exemplar{Labels: labels.FromStrings("cc", "dd", "xx", "yy"), Value: 4, HasTs: true, Ts: 123123},
|
||||||
}, {
|
}, {
|
||||||
m: "ii",
|
m: "ii",
|
||||||
typ: MetricTypeInfo,
|
typ: MetricTypeInfo,
|
||||||
|
@ -167,6 +191,15 @@ testmetric{label="\"bar\""} 1`
|
||||||
m: "testmetric{label=\"\\\"bar\\\"\"}",
|
m: "testmetric{label=\"\\\"bar\\\"\"}",
|
||||||
v: 1,
|
v: 1,
|
||||||
lset: labels.FromStrings("__name__", "testmetric", "label", `"bar"`),
|
lset: labels.FromStrings("__name__", "testmetric", "label", `"bar"`),
|
||||||
|
}, {
|
||||||
|
m: "foo",
|
||||||
|
typ: MetricTypeCounter,
|
||||||
|
}, {
|
||||||
|
m: "foo_total",
|
||||||
|
v: 17,
|
||||||
|
lset: labels.FromStrings("__name__", "foo_total"),
|
||||||
|
t: int64p(1520879607789),
|
||||||
|
e: &exemplar.Exemplar{Labels: labels.FromStrings("xx", "yy"), Value: 5},
|
||||||
}, {
|
}, {
|
||||||
m: "metric",
|
m: "metric",
|
||||||
help: "foo\x00bar",
|
help: "foo\x00bar",
|
||||||
|
@ -193,12 +226,20 @@ testmetric{label="\"bar\""} 1`
|
||||||
case EntrySeries:
|
case EntrySeries:
|
||||||
m, ts, v := p.Series()
|
m, ts, v := p.Series()
|
||||||
|
|
||||||
|
var e exemplar.Exemplar
|
||||||
p.Metric(&res)
|
p.Metric(&res)
|
||||||
|
found := p.Exemplar(&e)
|
||||||
|
|
||||||
testutil.Equals(t, exp[i].m, string(m))
|
testutil.Equals(t, exp[i].m, string(m))
|
||||||
testutil.Equals(t, exp[i].t, ts)
|
testutil.Equals(t, exp[i].t, ts)
|
||||||
testutil.Equals(t, exp[i].v, v)
|
testutil.Equals(t, exp[i].v, v)
|
||||||
testutil.Equals(t, exp[i].lset, res)
|
testutil.Equals(t, exp[i].lset, res)
|
||||||
|
if exp[i].e == nil {
|
||||||
|
testutil.Equals(t, false, found)
|
||||||
|
} else {
|
||||||
|
testutil.Equals(t, true, found)
|
||||||
|
testutil.Equals(t, *exp[i].e, e)
|
||||||
|
}
|
||||||
res = res[:0]
|
res = res[:0]
|
||||||
|
|
||||||
case EntryType:
|
case EntryType:
|
||||||
|
@ -232,11 +273,11 @@ func TestOpenMetricsParseErrors(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
input: "",
|
input: "",
|
||||||
err: "unexpected end of data, got \"EOF\"",
|
err: "EOF",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "a",
|
input: "a",
|
||||||
err: "expected value after metric, got \"MNAME\"",
|
err: "expected value after metric, got \"EOF\"",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "\n",
|
input: "\n",
|
||||||
|
@ -280,7 +321,7 @@ func TestOpenMetricsParseErrors(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "a\t1\n",
|
input: "a\t1\n",
|
||||||
err: "expected value after metric, got \"MNAME\"",
|
err: "expected value after metric, got \"INVALID\"",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "a 1\t2\n",
|
input: "a 1\t2\n",
|
||||||
|
@ -288,11 +329,11 @@ func TestOpenMetricsParseErrors(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "a 1 2 \n",
|
input: "a 1 2 \n",
|
||||||
err: "expected next entry after timestamp, got \"MNAME\"",
|
err: "expected next entry after timestamp, got \"INVALID\"",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "a 1 2 #\n",
|
input: "a 1 2 #\n",
|
||||||
err: "expected next entry after timestamp, got \"MNAME\"",
|
err: "expected next entry after timestamp, got \"TIMESTAMP\"",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "a 1 1z\n",
|
input: "a 1 1z\n",
|
||||||
|
@ -324,7 +365,7 @@ func TestOpenMetricsParseErrors(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "a 1 1 1\n",
|
input: "a 1 1 1\n",
|
||||||
err: "expected next entry after timestamp, got \"MNAME\"",
|
err: "expected next entry after timestamp, got \"TIMESTAMP\"",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "a{b='c'} 1\n",
|
input: "a{b='c'} 1\n",
|
||||||
|
@ -386,6 +427,42 @@ func TestOpenMetricsParseErrors(t *testing.T) {
|
||||||
input: "foo 0 1_2\n",
|
input: "foo 0 1_2\n",
|
||||||
err: "unsupported character in float",
|
err: "unsupported character in float",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
input: "custom_metric_total 1 # {aa=bb}",
|
||||||
|
err: "expected label value, got \"INVALID\"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: `custom_metric_total 1 # {aa="bb"}`,
|
||||||
|
err: "expected value after exemplar labels, got \"EOF\"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: `custom_metric 1 # {aa="bb"}`,
|
||||||
|
err: "metric name custom_metric does not support exemplars",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: `custom_metric_total 1 # {aa="bb",,cc="dd"} 1`,
|
||||||
|
err: "expected label name, got \"COMMA\"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: `custom_metric_total 1 # {aa="bb"} 1_2`,
|
||||||
|
err: "unsupported character in float",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: `custom_metric_total 1 # {aa="bb"} 0x1p-3`,
|
||||||
|
err: "unsupported character in float",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: `custom_metric_total 1 # {aa="bb"} true`,
|
||||||
|
err: "strconv.ParseFloat: parsing \"true\": invalid syntax",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: `custom_metric_total 1 # {aa="bb",cc=}`,
|
||||||
|
err: "expected label value, got \"INVALID\"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: `custom_metric_total 1 # {aa=\"\xff\"} 9.0`,
|
||||||
|
err: "expected label value, got \"INVALID\"",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, c := range cases {
|
for i, c := range cases {
|
||||||
|
@ -433,7 +510,7 @@ func TestOMNullByteHandling(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "a\x00{b=\"ddd\"} 1",
|
input: "a\x00{b=\"ddd\"} 1",
|
||||||
err: "expected value after metric, got \"MNAME\"",
|
err: "expected value after metric, got \"INVALID\"",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "#",
|
input: "#",
|
||||||
|
@ -443,6 +520,14 @@ func TestOMNullByteHandling(t *testing.T) {
|
||||||
input: "# H",
|
input: "# H",
|
||||||
err: "\"INVALID\" \" \" is not a valid start token",
|
err: "\"INVALID\" \" \" is not a valid start token",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
input: "custom_metric_total 1 # {b=\x00\"ssss\"} 1\n",
|
||||||
|
err: "expected label value, got \"INVALID\"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "custom_metric_total 1 # {b=\"\x00ss\"} 1\n",
|
||||||
|
err: "expected label value, got \"INVALID\"",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, c := range cases {
|
for i, c := range cases {
|
||||||
|
|
|
@ -28,6 +28,9 @@ const (
|
||||||
sLValue
|
sLValue
|
||||||
sValue
|
sValue
|
||||||
sTimestamp
|
sTimestamp
|
||||||
|
sExemplar
|
||||||
|
sEValue
|
||||||
|
sETimestamp
|
||||||
)
|
)
|
||||||
|
|
||||||
// Lex is called by the parser generated by "go tool yacc" to obtain each
|
// Lex is called by the parser generated by "go tool yacc" to obtain each
|
||||||
|
|
|
@ -28,6 +28,7 @@ import (
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/prometheus/prometheus/pkg/exemplar"
|
||||||
"github.com/prometheus/prometheus/pkg/labels"
|
"github.com/prometheus/prometheus/pkg/labels"
|
||||||
"github.com/prometheus/prometheus/pkg/value"
|
"github.com/prometheus/prometheus/pkg/value"
|
||||||
)
|
)
|
||||||
|
@ -234,6 +235,12 @@ func (p *PromParser) Metric(l *labels.Labels) string {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Exemplar writes the exemplar of the current sample into the passed
|
||||||
|
// exemplar. It returns if an exemplar exists.
|
||||||
|
func (p *PromParser) Exemplar(e *exemplar.Exemplar) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// nextToken returns the next token from the promlexer. It skips over tabs
|
// nextToken returns the next token from the promlexer. It skips over tabs
|
||||||
// and spaces.
|
// and spaces.
|
||||||
func (p *PromParser) nextToken() token {
|
func (p *PromParser) nextToken() token {
|
||||||
|
|
Loading…
Reference in a new issue