mirror of
https://github.com/prometheus/prometheus.git
synced 2024-12-25 13:44:05 -08:00
Add time series description parsing.
This commit adds parsing of time series description to the exisiting query language parser. Time series descriptions are defined by a metric followed by a sequence of values.
This commit is contained in:
parent
d122749b39
commit
a236c01457
|
@ -284,6 +284,10 @@ type lexer struct {
|
|||
braceOpen bool // Whether a { is opened.
|
||||
bracketOpen bool // Whether a [ is opened.
|
||||
stringOpen rune // Quote rune of the string currently being read.
|
||||
|
||||
// seriesDesc is set when a series description for the testing
|
||||
// language is lexed.
|
||||
seriesDesc bool
|
||||
}
|
||||
|
||||
// next returns the next rune in the input.
|
||||
|
@ -536,6 +540,10 @@ func lexInsideBraces(l *lexer) stateFn {
|
|||
case r == '}':
|
||||
l.emit(itemRightBrace)
|
||||
l.braceOpen = false
|
||||
|
||||
if l.seriesDesc {
|
||||
return lexValueSequence
|
||||
}
|
||||
return lexStatements
|
||||
default:
|
||||
return l.errorf("unexpected character inside braces: %q", r)
|
||||
|
@ -670,7 +678,7 @@ func (l *lexer) scanNumber() bool {
|
|||
l.acceptRun("0123456789")
|
||||
}
|
||||
// Next thing must not be alphanumeric.
|
||||
if isAlphaNumeric(l.peek()) {
|
||||
if isAlphaNumeric(l.peek()) && !l.seriesDesc {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
@ -709,6 +717,9 @@ Loop:
|
|||
break Loop
|
||||
}
|
||||
}
|
||||
if l.seriesDesc && l.peek() != '{' {
|
||||
return lexValueSequence
|
||||
}
|
||||
return lexStatements
|
||||
}
|
||||
|
||||
|
|
|
@ -20,9 +20,10 @@ import (
|
|||
)
|
||||
|
||||
var tests = []struct {
|
||||
input string
|
||||
expected []item
|
||||
fail bool
|
||||
input string
|
||||
expected []item
|
||||
fail bool
|
||||
seriesDesc bool // Whether to lex a series description.
|
||||
}{
|
||||
// Test common stuff.
|
||||
{
|
||||
|
@ -398,6 +399,7 @@ var tests = []struct {
|
|||
func TestLexer(t *testing.T) {
|
||||
for i, test := range tests {
|
||||
l := lex(test.input)
|
||||
l.seriesDesc = test.seriesDesc
|
||||
|
||||
out := []item{}
|
||||
for it := range l.items {
|
||||
|
|
107
promql/parse.go
107
promql/parse.go
|
@ -70,6 +70,14 @@ func ParseExpr(input string) (Expr, error) {
|
|||
return expr, err
|
||||
}
|
||||
|
||||
// parseSeriesDesc parses the description of a time series.
|
||||
func parseSeriesDesc(input string) (clientmodel.Metric, []sequenceValue, error) {
|
||||
p := newParser(input)
|
||||
p.lex.seriesDesc = true
|
||||
|
||||
return p.parseSeriesDesc()
|
||||
}
|
||||
|
||||
// newParser returns a new parser.
|
||||
func newParser(input string) *parser {
|
||||
p := &parser{
|
||||
|
@ -112,6 +120,105 @@ func (p *parser) parseExpr() (expr Expr, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
// sequenceValue is a omittable value in a sequence of time series values.
|
||||
type sequenceValue struct {
|
||||
value clientmodel.SampleValue
|
||||
omitted bool
|
||||
}
|
||||
|
||||
func (v sequenceValue) String() string {
|
||||
if v.omitted {
|
||||
return "_"
|
||||
}
|
||||
return v.value.String()
|
||||
}
|
||||
|
||||
// parseSeriesDesc parses a description of a time series into its metric and value sequence.
|
||||
func (p *parser) parseSeriesDesc() (m clientmodel.Metric, vals []sequenceValue, err error) {
|
||||
defer p.recover(&err)
|
||||
|
||||
name := ""
|
||||
m = clientmodel.Metric{}
|
||||
|
||||
t := p.peek().typ
|
||||
if t == itemIdentifier || t == itemMetricIdentifier {
|
||||
name = p.next().val
|
||||
t = p.peek().typ
|
||||
}
|
||||
if t == itemLeftBrace {
|
||||
m = clientmodel.Metric(p.labelSet())
|
||||
}
|
||||
if name != "" {
|
||||
m[clientmodel.MetricNameLabel] = clientmodel.LabelValue(name)
|
||||
}
|
||||
|
||||
const ctx = "series values"
|
||||
for {
|
||||
if p.peek().typ == itemEOF {
|
||||
break
|
||||
}
|
||||
|
||||
// Extract blanks.
|
||||
if p.peek().typ == itemBlank {
|
||||
p.next()
|
||||
times := uint64(1)
|
||||
if p.peek().typ == itemTimes {
|
||||
p.next()
|
||||
times, err = strconv.ParseUint(p.expect(itemNumber, ctx).val, 10, 64)
|
||||
if err != nil {
|
||||
p.errorf("invalid repetition in %s: %s", ctx, err)
|
||||
}
|
||||
}
|
||||
for i := uint64(0); i < times; i++ {
|
||||
vals = append(vals, sequenceValue{omitted: true})
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Extract values.
|
||||
sign := 1.0
|
||||
if t := p.peek().typ; t == itemSUB || t == itemADD {
|
||||
if p.next().typ == itemSUB {
|
||||
sign = -1
|
||||
}
|
||||
}
|
||||
k := sign * p.number(p.expect(itemNumber, ctx).val)
|
||||
vals = append(vals, sequenceValue{
|
||||
value: clientmodel.SampleValue(k),
|
||||
})
|
||||
|
||||
// If there are no offset repetitions specified, proceed with the next value.
|
||||
if t := p.peek().typ; t == itemNumber || t == itemBlank {
|
||||
continue
|
||||
} else if t == itemEOF {
|
||||
break
|
||||
} else if t != itemADD && t != itemSUB {
|
||||
p.errorf("expected next value or relative expansion in %s but got %s", ctx, t.desc())
|
||||
}
|
||||
|
||||
// Expand the repeated offsets into values.
|
||||
sign = 1.0
|
||||
if p.next().typ == itemSUB {
|
||||
sign = -1.0
|
||||
}
|
||||
offset := sign * p.number(p.expect(itemNumber, ctx).val)
|
||||
p.expect(itemTimes, ctx)
|
||||
|
||||
times, err := strconv.ParseUint(p.expect(itemNumber, ctx).val, 10, 64)
|
||||
if err != nil {
|
||||
p.errorf("invalid repetition in %s: %s", ctx, err)
|
||||
}
|
||||
|
||||
for i := uint64(0); i < times; i++ {
|
||||
k += offset
|
||||
vals = append(vals, sequenceValue{
|
||||
value: clientmodel.SampleValue(k),
|
||||
})
|
||||
}
|
||||
}
|
||||
return m, vals, nil
|
||||
}
|
||||
|
||||
// typecheck checks correct typing of the parsed statements or expression.
|
||||
func (p *parser) typecheck(node Node) (err error) {
|
||||
defer p.recover(&err)
|
||||
|
|
|
@ -1216,3 +1216,101 @@ func mustGetFunction(name string) *Function {
|
|||
}
|
||||
return f
|
||||
}
|
||||
|
||||
var testSeries = []struct {
|
||||
input string
|
||||
expectedMetric clientmodel.Metric
|
||||
expectedValues []sequenceValue
|
||||
fail bool
|
||||
}{
|
||||
{
|
||||
input: `{} 1 2 3`,
|
||||
expectedMetric: clientmodel.Metric{},
|
||||
expectedValues: newSeq(1, 2, 3),
|
||||
}, {
|
||||
input: `{a="b"} -1 2 3`,
|
||||
expectedMetric: clientmodel.Metric{
|
||||
"a": "b",
|
||||
},
|
||||
expectedValues: newSeq(-1, 2, 3),
|
||||
}, {
|
||||
input: `my_metric 1 2 3`,
|
||||
expectedMetric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "my_metric",
|
||||
},
|
||||
expectedValues: newSeq(1, 2, 3),
|
||||
}, {
|
||||
input: `my_metric{} 1 2 3`,
|
||||
expectedMetric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "my_metric",
|
||||
},
|
||||
expectedValues: newSeq(1, 2, 3),
|
||||
}, {
|
||||
input: `my_metric{a="b"} 1 2 3`,
|
||||
expectedMetric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "my_metric",
|
||||
"a": "b",
|
||||
},
|
||||
expectedValues: newSeq(1, 2, 3),
|
||||
}, {
|
||||
input: `my_metric{a="b"} 1 2 3-10x4`,
|
||||
expectedMetric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "my_metric",
|
||||
"a": "b",
|
||||
},
|
||||
expectedValues: newSeq(1, 2, 3, -7, -17, -27, -37),
|
||||
}, {
|
||||
input: `my_metric{a="b"} 1 3 _ 5 _x4`,
|
||||
expectedMetric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "my_metric",
|
||||
"a": "b",
|
||||
},
|
||||
expectedValues: newSeq(1, 3, none, 5, none, none, none, none),
|
||||
}, {
|
||||
input: `my_metric{a="b"} 1 3 _ 5 _a4`,
|
||||
fail: true,
|
||||
},
|
||||
}
|
||||
|
||||
// For these tests only, we use the samallest float64 to signal an omitted value.
|
||||
const none = math.SmallestNonzeroFloat64
|
||||
|
||||
func newSeq(vals ...float64) (res []sequenceValue) {
|
||||
for _, v := range vals {
|
||||
if v == none {
|
||||
res = append(res, sequenceValue{omitted: true})
|
||||
} else {
|
||||
res = append(res, sequenceValue{value: clientmodel.SampleValue(v)})
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func TestParseSeries(t *testing.T) {
|
||||
for _, test := range testSeries {
|
||||
parser := newParser(test.input)
|
||||
parser.lex.seriesDesc = true
|
||||
|
||||
metric, vals, err := parser.parseSeriesDesc()
|
||||
if !test.fail && err != nil {
|
||||
t.Errorf("error in input: \n\n%s\n", test.input)
|
||||
t.Fatalf("could not parse: %s", err)
|
||||
}
|
||||
if test.fail && err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if test.fail {
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
t.Errorf("error in input: \n\n%s\n", test.input)
|
||||
t.Fatalf("failure expected, but passed")
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(vals, test.expectedValues) || !reflect.DeepEqual(metric, test.expectedMetric) {
|
||||
t.Errorf("error in input: \n\n%s\n", test.input)
|
||||
t.Fatalf("no match\n\nexpected:\n%s %s\ngot: \n%s %s\n", test.expectedMetric, test.expectedValues, metric, vals)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue