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:
Fabian Reinartz 2015-05-11 14:04:53 +02:00
parent d122749b39
commit a236c01457
4 changed files with 222 additions and 4 deletions

View file

@ -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
}

View file

@ -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 {

View file

@ -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)

View file

@ -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)
}
}
}