mirror of
https://github.com/prometheus/prometheus.git
synced 2025-03-05 20:59:13 -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.
|
braceOpen bool // Whether a { is opened.
|
||||||
bracketOpen bool // Whether a [ is opened.
|
bracketOpen bool // Whether a [ is opened.
|
||||||
stringOpen rune // Quote rune of the string currently being read.
|
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.
|
// next returns the next rune in the input.
|
||||||
|
@ -536,6 +540,10 @@ func lexInsideBraces(l *lexer) stateFn {
|
||||||
case r == '}':
|
case r == '}':
|
||||||
l.emit(itemRightBrace)
|
l.emit(itemRightBrace)
|
||||||
l.braceOpen = false
|
l.braceOpen = false
|
||||||
|
|
||||||
|
if l.seriesDesc {
|
||||||
|
return lexValueSequence
|
||||||
|
}
|
||||||
return lexStatements
|
return lexStatements
|
||||||
default:
|
default:
|
||||||
return l.errorf("unexpected character inside braces: %q", r)
|
return l.errorf("unexpected character inside braces: %q", r)
|
||||||
|
@ -670,7 +678,7 @@ func (l *lexer) scanNumber() bool {
|
||||||
l.acceptRun("0123456789")
|
l.acceptRun("0123456789")
|
||||||
}
|
}
|
||||||
// Next thing must not be alphanumeric.
|
// Next thing must not be alphanumeric.
|
||||||
if isAlphaNumeric(l.peek()) {
|
if isAlphaNumeric(l.peek()) && !l.seriesDesc {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
@ -709,6 +717,9 @@ Loop:
|
||||||
break Loop
|
break Loop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if l.seriesDesc && l.peek() != '{' {
|
||||||
|
return lexValueSequence
|
||||||
|
}
|
||||||
return lexStatements
|
return lexStatements
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ var tests = []struct {
|
||||||
input string
|
input string
|
||||||
expected []item
|
expected []item
|
||||||
fail bool
|
fail bool
|
||||||
|
seriesDesc bool // Whether to lex a series description.
|
||||||
}{
|
}{
|
||||||
// Test common stuff.
|
// Test common stuff.
|
||||||
{
|
{
|
||||||
|
@ -398,6 +399,7 @@ var tests = []struct {
|
||||||
func TestLexer(t *testing.T) {
|
func TestLexer(t *testing.T) {
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
l := lex(test.input)
|
l := lex(test.input)
|
||||||
|
l.seriesDesc = test.seriesDesc
|
||||||
|
|
||||||
out := []item{}
|
out := []item{}
|
||||||
for it := range l.items {
|
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
|
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.
|
// newParser returns a new parser.
|
||||||
func newParser(input string) *parser {
|
func newParser(input string) *parser {
|
||||||
p := &parser{
|
p := &parser{
|
||||||
|
@ -112,6 +120,105 @@ func (p *parser) parseExpr() (expr Expr, err error) {
|
||||||
return
|
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.
|
// typecheck checks correct typing of the parsed statements or expression.
|
||||||
func (p *parser) typecheck(node Node) (err error) {
|
func (p *parser) typecheck(node Node) (err error) {
|
||||||
defer p.recover(&err)
|
defer p.recover(&err)
|
||||||
|
|
|
@ -1216,3 +1216,101 @@ func mustGetFunction(name string) *Function {
|
||||||
}
|
}
|
||||||
return f
|
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