mirror of
https://github.com/prometheus/prometheus.git
synced 2024-12-24 21:24:05 -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 (
|
||||
"mime"
|
||||
|
||||
"github.com/prometheus/prometheus/pkg/exemplar"
|
||||
"github.com/prometheus/prometheus/pkg/labels"
|
||||
)
|
||||
|
||||
|
@ -50,6 +51,10 @@ type Parser interface {
|
|||
// It returns the string from which the metric was parsed.
|
||||
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
|
||||
// more samples were read or an error occurred.
|
||||
Next() (Entry, error)
|
||||
|
|
|
@ -36,7 +36,7 @@ M [a-zA-Z_:]
|
|||
C [^\n]
|
||||
S [ ]
|
||||
|
||||
%x sComment sMeta1 sMeta2 sLabels sLValue sValue sTimestamp
|
||||
%x sComment sMeta1 sMeta2 sLabels sLValue sValue sTimestamp sExemplar sEValue sETimestamp
|
||||
|
||||
%yyc c
|
||||
%yyn c = l.next()
|
||||
|
@ -62,8 +62,17 @@ S [ ]
|
|||
<sLValue>\"(\\.|[^\\"\n])*\" l.state = sLabels; return tLValue
|
||||
<sValue>{S}[^ \n]+ l.state = sTimestamp; return tValue
|
||||
<sTimestamp>{S}[^ \n]+ return tTimestamp
|
||||
<sTimestamp>{S}#{S}{C}*\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
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
@ -16,7 +16,7 @@
|
|||
package textparse
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Lex is called by the parser generated by "go tool yacc" to obtain each
|
||||
|
@ -33,7 +33,7 @@ yystate0:
|
|||
|
||||
switch yyt := l.state; yyt {
|
||||
default:
|
||||
panic(errors.Errorf(`invalid start condition %d`, yyt))
|
||||
panic(fmt.Errorf(`invalid start condition %d`, yyt))
|
||||
case 0: // start condition: INITIAL
|
||||
goto yystart1
|
||||
case 1: // start condition: sComment
|
||||
|
@ -50,6 +50,12 @@ yystate0:
|
|||
goto yystart39
|
||||
case 7: // start condition: sTimestamp
|
||||
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
|
||||
|
@ -427,7 +433,7 @@ yystart43:
|
|||
|
||||
yystate44:
|
||||
c = l.next()
|
||||
goto yyrule18
|
||||
goto yyrule17
|
||||
|
||||
yystate45:
|
||||
c = l.next()
|
||||
|
@ -465,15 +471,143 @@ yystate48:
|
|||
switch {
|
||||
default:
|
||||
goto yyabort
|
||||
case c == '\n':
|
||||
case c == '{':
|
||||
goto yystate49
|
||||
case c >= '\x01' && c <= '\t' || c >= '\v' && c <= 'ÿ':
|
||||
goto yystate48
|
||||
}
|
||||
|
||||
yystate49:
|
||||
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}
|
||||
{
|
||||
|
@ -564,13 +698,55 @@ yyrule16: // {S}[^ \n]+
|
|||
{
|
||||
return tTimestamp
|
||||
}
|
||||
yyrule17: // {S}#{S}{C}*\n
|
||||
yyrule17: // \n
|
||||
{
|
||||
l.state = sInit
|
||||
return tLinebreak
|
||||
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
|
||||
return tLinebreak
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
package textparse
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"sort"
|
||||
|
@ -25,10 +27,13 @@ import (
|
|||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/prometheus/prometheus/pkg/exemplar"
|
||||
"github.com/prometheus/prometheus/pkg/labels"
|
||||
"github.com/prometheus/prometheus/pkg/value"
|
||||
)
|
||||
|
||||
var allowedSuffixes = [][]byte{[]byte("_total"), []byte("_bucket")}
|
||||
|
||||
type openMetricsLexer struct {
|
||||
b []byte
|
||||
i int
|
||||
|
@ -85,6 +90,12 @@ type OpenMetricsParser struct {
|
|||
hasTS bool
|
||||
start int
|
||||
offsets []int
|
||||
|
||||
eOffsets []int
|
||||
exemplar []byte
|
||||
exemplarVal float64
|
||||
exemplarTs int64
|
||||
hasExemplarTs bool
|
||||
}
|
||||
|
||||
// NewOpenMetricsParser returns a new parser of the byte slice.
|
||||
|
@ -96,7 +107,8 @@ func NewOpenMetricsParser(b []byte) Parser {
|
|||
// of the current sample.
|
||||
func (p *OpenMetricsParser) Series() ([]byte, *int64, float64) {
|
||||
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
|
||||
}
|
||||
|
@ -170,6 +182,38 @@ func (p *OpenMetricsParser) Metric(l *labels.Labels) string {
|
|||
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.
|
||||
func (p *OpenMetricsParser) nextToken() token {
|
||||
tok := p.l.Lex()
|
||||
|
@ -183,6 +227,10 @@ func (p *OpenMetricsParser) Next() (Entry, error) {
|
|||
|
||||
p.start = p.l.i
|
||||
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 {
|
||||
case tEofWord:
|
||||
|
@ -191,7 +239,7 @@ func (p *OpenMetricsParser) Next() (Entry, error) {
|
|||
}
|
||||
return EntryInvalid, io.EOF
|
||||
case tEOF:
|
||||
return EntryInvalid, parseError("unexpected end of data", t)
|
||||
return EntryInvalid, io.EOF
|
||||
case tHelp, tType, tUnit:
|
||||
switch t := p.nextToken(); t {
|
||||
case tMName:
|
||||
|
@ -258,26 +306,29 @@ func (p *OpenMetricsParser) Next() (Entry, error) {
|
|||
|
||||
t2 := p.nextToken()
|
||||
if t2 == tBraceOpen {
|
||||
if err := p.parseLVals(); err != nil {
|
||||
offsets, err := p.parseLVals()
|
||||
if err != nil {
|
||||
return EntryInvalid, err
|
||||
}
|
||||
p.offsets = append(p.offsets, offsets...)
|
||||
p.series = p.l.b[p.start:p.l.i]
|
||||
t2 = p.nextToken()
|
||||
}
|
||||
if t2 != tValue {
|
||||
return EntryInvalid, parseError("expected value after metric", t)
|
||||
}
|
||||
if p.val, err = parseFloat(yoloString(p.l.buf()[1:])); err != nil {
|
||||
p.val, err = p.getFloatValue(t2, "metric")
|
||||
if err != nil {
|
||||
return EntryInvalid, err
|
||||
}
|
||||
// Ensure canonical NaN value.
|
||||
if math.IsNaN(p.val) {
|
||||
p.val = math.Float64frombits(value.NormalNaN)
|
||||
}
|
||||
|
||||
p.hasTS = false
|
||||
switch p.nextToken() {
|
||||
switch t2 := p.nextToken(); t2 {
|
||||
case tEOF:
|
||||
return EntryInvalid, io.EOF
|
||||
case tLinebreak:
|
||||
break
|
||||
case tComment:
|
||||
if err := p.parseComment(); err != nil {
|
||||
return EntryInvalid, err
|
||||
}
|
||||
case tTimestamp:
|
||||
p.hasTS = true
|
||||
var ts float64
|
||||
|
@ -286,11 +337,17 @@ func (p *OpenMetricsParser) Next() (Entry, error) {
|
|||
return EntryInvalid, err
|
||||
}
|
||||
p.ts = int64(ts * 1000)
|
||||
if t2 := p.nextToken(); t2 != tLinebreak {
|
||||
return EntryInvalid, parseError("expected next entry after timestamp", t)
|
||||
switch t3 := p.nextToken(); t3 {
|
||||
case tLinebreak:
|
||||
case tComment:
|
||||
if err := p.parseComment(); err != nil {
|
||||
return EntryInvalid, err
|
||||
}
|
||||
default:
|
||||
return EntryInvalid, parseError("expected next entry after timestamp", t3)
|
||||
}
|
||||
default:
|
||||
return EntryInvalid, parseError("expected timestamp or new record", t)
|
||||
return EntryInvalid, parseError("expected timestamp or # symbol", t2)
|
||||
}
|
||||
return EntrySeries, nil
|
||||
|
||||
|
@ -300,50 +357,121 @@ func (p *OpenMetricsParser) Next() (Entry, error) {
|
|||
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
|
||||
for {
|
||||
t := p.nextToken()
|
||||
switch t {
|
||||
case tBraceClose:
|
||||
return nil
|
||||
return offsets, nil
|
||||
case tComma:
|
||||
if first {
|
||||
return parseError("expected label name or left brace", t)
|
||||
return nil, parseError("expected label name or left brace", t)
|
||||
}
|
||||
t = p.nextToken()
|
||||
if t != tLName {
|
||||
return parseError("expected label name", t)
|
||||
return nil, parseError("expected label name", t)
|
||||
}
|
||||
case tLName:
|
||||
if !first {
|
||||
return parseError("expected comma", t)
|
||||
return nil, parseError("expected comma", t)
|
||||
}
|
||||
default:
|
||||
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
|
||||
// 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 {
|
||||
return parseError("expected equal", t)
|
||||
return nil, parseError("expected equal", t)
|
||||
}
|
||||
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()) {
|
||||
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
|
||||
// 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"
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus/prometheus/pkg/exemplar"
|
||||
"github.com/prometheus/prometheus/pkg/labels"
|
||||
"github.com/prometheus/prometheus/util/testutil"
|
||||
)
|
||||
|
@ -38,9 +39,13 @@ some:aggregate:rate5m{a_b="c"} 1
|
|||
# TYPE go_goroutines gauge
|
||||
go_goroutines 33 123.123
|
||||
# TYPE hh histogram
|
||||
hh_bucket{le="+Inf"} 1 # {} 4
|
||||
hh_bucket{le="+Inf"} 1
|
||||
# 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
|
||||
ii{foo="bar"} 1
|
||||
# TYPE ss stateset
|
||||
|
@ -49,7 +54,9 @@ ss{ss="bar"} 0
|
|||
# TYPE un unknown
|
||||
_metric_starting_with_underscore 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 += "\nnull_byte_metric{a=\"abc\x00\"} 1"
|
||||
|
@ -66,6 +73,7 @@ testmetric{label="\"bar\""} 1`
|
|||
help string
|
||||
unit string
|
||||
comment string
|
||||
e *exemplar.Exemplar
|
||||
}{
|
||||
{
|
||||
m: "go_gc_duration_seconds",
|
||||
|
@ -134,6 +142,22 @@ testmetric{label="\"bar\""} 1`
|
|||
m: `gh_bucket{le="+Inf"}`,
|
||||
v: 1,
|
||||
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",
|
||||
typ: MetricTypeInfo,
|
||||
|
@ -167,6 +191,15 @@ testmetric{label="\"bar\""} 1`
|
|||
m: "testmetric{label=\"\\\"bar\\\"\"}",
|
||||
v: 1,
|
||||
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",
|
||||
help: "foo\x00bar",
|
||||
|
@ -193,12 +226,20 @@ testmetric{label="\"bar\""} 1`
|
|||
case EntrySeries:
|
||||
m, ts, v := p.Series()
|
||||
|
||||
var e exemplar.Exemplar
|
||||
p.Metric(&res)
|
||||
found := p.Exemplar(&e)
|
||||
|
||||
testutil.Equals(t, exp[i].m, string(m))
|
||||
testutil.Equals(t, exp[i].t, ts)
|
||||
testutil.Equals(t, exp[i].v, v)
|
||||
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]
|
||||
|
||||
case EntryType:
|
||||
|
@ -232,11 +273,11 @@ func TestOpenMetricsParseErrors(t *testing.T) {
|
|||
}{
|
||||
{
|
||||
input: "",
|
||||
err: "unexpected end of data, got \"EOF\"",
|
||||
err: "EOF",
|
||||
},
|
||||
{
|
||||
input: "a",
|
||||
err: "expected value after metric, got \"MNAME\"",
|
||||
err: "expected value after metric, got \"EOF\"",
|
||||
},
|
||||
{
|
||||
input: "\n",
|
||||
|
@ -280,7 +321,7 @@ func TestOpenMetricsParseErrors(t *testing.T) {
|
|||
},
|
||||
{
|
||||
input: "a\t1\n",
|
||||
err: "expected value after metric, got \"MNAME\"",
|
||||
err: "expected value after metric, got \"INVALID\"",
|
||||
},
|
||||
{
|
||||
input: "a 1\t2\n",
|
||||
|
@ -288,11 +329,11 @@ func TestOpenMetricsParseErrors(t *testing.T) {
|
|||
},
|
||||
{
|
||||
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",
|
||||
err: "expected next entry after timestamp, got \"MNAME\"",
|
||||
err: "expected next entry after timestamp, got \"TIMESTAMP\"",
|
||||
},
|
||||
{
|
||||
input: "a 1 1z\n",
|
||||
|
@ -324,7 +365,7 @@ func TestOpenMetricsParseErrors(t *testing.T) {
|
|||
},
|
||||
{
|
||||
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",
|
||||
|
@ -386,6 +427,42 @@ func TestOpenMetricsParseErrors(t *testing.T) {
|
|||
input: "foo 0 1_2\n",
|
||||
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 {
|
||||
|
@ -433,7 +510,7 @@ func TestOMNullByteHandling(t *testing.T) {
|
|||
},
|
||||
{
|
||||
input: "a\x00{b=\"ddd\"} 1",
|
||||
err: "expected value after metric, got \"MNAME\"",
|
||||
err: "expected value after metric, got \"INVALID\"",
|
||||
},
|
||||
{
|
||||
input: "#",
|
||||
|
@ -443,6 +520,14 @@ func TestOMNullByteHandling(t *testing.T) {
|
|||
input: "# H",
|
||||
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 {
|
||||
|
|
|
@ -28,6 +28,9 @@ const (
|
|||
sLValue
|
||||
sValue
|
||||
sTimestamp
|
||||
sExemplar
|
||||
sEValue
|
||||
sETimestamp
|
||||
)
|
||||
|
||||
// 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/prometheus/prometheus/pkg/exemplar"
|
||||
"github.com/prometheus/prometheus/pkg/labels"
|
||||
"github.com/prometheus/prometheus/pkg/value"
|
||||
)
|
||||
|
@ -234,6 +235,12 @@ func (p *PromParser) Metric(l *labels.Labels) string {
|
|||
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
|
||||
// and spaces.
|
||||
func (p *PromParser) nextToken() token {
|
||||
|
|
Loading…
Reference in a new issue