mirror of
https://github.com/prometheus/prometheus.git
synced 2025-03-05 20:59:13 -08:00
promql: Remove old and unused alerting/reconding syntax
Signed-off-by: Frederic Branczyk <fbranczyk@gmail.com>
This commit is contained in:
parent
cc878ea559
commit
b0b3e3dd74
|
@ -16,7 +16,6 @@ package main
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"math"
|
"math"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
@ -26,16 +25,13 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gopkg.in/alecthomas/kingpin.v2"
|
"gopkg.in/alecthomas/kingpin.v2"
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/api"
|
"github.com/prometheus/client_golang/api"
|
||||||
"github.com/prometheus/client_golang/api/prometheus/v1"
|
"github.com/prometheus/client_golang/api/prometheus/v1"
|
||||||
config_util "github.com/prometheus/common/config"
|
config_util "github.com/prometheus/common/config"
|
||||||
"github.com/prometheus/common/model"
|
|
||||||
"github.com/prometheus/common/version"
|
"github.com/prometheus/common/version"
|
||||||
"github.com/prometheus/prometheus/config"
|
"github.com/prometheus/prometheus/config"
|
||||||
"github.com/prometheus/prometheus/pkg/rulefmt"
|
"github.com/prometheus/prometheus/pkg/rulefmt"
|
||||||
"github.com/prometheus/prometheus/promql"
|
|
||||||
"github.com/prometheus/prometheus/util/promlint"
|
"github.com/prometheus/prometheus/util/promlint"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -60,10 +56,6 @@ func main() {
|
||||||
|
|
||||||
checkMetricsCmd := checkCmd.Command("metrics", checkMetricsUsage)
|
checkMetricsCmd := checkCmd.Command("metrics", checkMetricsUsage)
|
||||||
|
|
||||||
updateCmd := app.Command("update", "Update the resources to newer formats.")
|
|
||||||
updateRulesCmd := updateCmd.Command("rules", "Update rules from the 1.x to 2.x format.")
|
|
||||||
ruleFilesUp := updateRulesCmd.Arg("rule-files", "The rule files to update.").Required().ExistingFiles()
|
|
||||||
|
|
||||||
queryCmd := app.Command("query", "Run query against a Prometheus server.")
|
queryCmd := app.Command("query", "Run query against a Prometheus server.")
|
||||||
queryInstantCmd := queryCmd.Command("instant", "Run instant query.")
|
queryInstantCmd := queryCmd.Command("instant", "Run instant query.")
|
||||||
queryServer := queryInstantCmd.Arg("server", "Prometheus server to query.").Required().String()
|
queryServer := queryInstantCmd.Arg("server", "Prometheus server to query.").Required().String()
|
||||||
|
@ -104,9 +96,6 @@ func main() {
|
||||||
case checkMetricsCmd.FullCommand():
|
case checkMetricsCmd.FullCommand():
|
||||||
os.Exit(CheckMetrics())
|
os.Exit(CheckMetrics())
|
||||||
|
|
||||||
case updateRulesCmd.FullCommand():
|
|
||||||
os.Exit(UpdateRules(*ruleFilesUp...))
|
|
||||||
|
|
||||||
case queryInstantCmd.FullCommand():
|
case queryInstantCmd.FullCommand():
|
||||||
os.Exit(QueryInstant(*queryServer, *queryExpr))
|
os.Exit(QueryInstant(*queryServer, *queryExpr))
|
||||||
|
|
||||||
|
@ -286,74 +275,6 @@ func checkRules(filename string) (int, []error) {
|
||||||
return numRules, nil
|
return numRules, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateRules updates the rule files.
|
|
||||||
func UpdateRules(files ...string) int {
|
|
||||||
failed := false
|
|
||||||
|
|
||||||
for _, f := range files {
|
|
||||||
if err := updateRules(f); err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, " FAILED:", err)
|
|
||||||
failed = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if failed {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateRules(filename string) error {
|
|
||||||
fmt.Println("Updating", filename)
|
|
||||||
|
|
||||||
content, err := ioutil.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
rules, err := promql.ParseStmts(string(content))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
yamlRG := &rulefmt.RuleGroups{
|
|
||||||
Groups: []rulefmt.RuleGroup{{
|
|
||||||
Name: filename,
|
|
||||||
}},
|
|
||||||
}
|
|
||||||
|
|
||||||
yamlRules := make([]rulefmt.Rule, 0, len(rules))
|
|
||||||
|
|
||||||
for _, rule := range rules {
|
|
||||||
switch r := rule.(type) {
|
|
||||||
case *promql.AlertStmt:
|
|
||||||
yamlRules = append(yamlRules, rulefmt.Rule{
|
|
||||||
Alert: r.Name,
|
|
||||||
Expr: r.Expr.String(),
|
|
||||||
For: model.Duration(r.Duration),
|
|
||||||
Labels: r.Labels.Map(),
|
|
||||||
Annotations: r.Annotations.Map(),
|
|
||||||
})
|
|
||||||
case *promql.RecordStmt:
|
|
||||||
yamlRules = append(yamlRules, rulefmt.Rule{
|
|
||||||
Record: r.Name,
|
|
||||||
Expr: r.Expr.String(),
|
|
||||||
Labels: r.Labels.Map(),
|
|
||||||
})
|
|
||||||
default:
|
|
||||||
panic("unknown statement type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
yamlRG.Groups[0].Rules = yamlRules
|
|
||||||
y, err := yaml.Marshal(yamlRG)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return ioutil.WriteFile(filename+".yml", y, 0666)
|
|
||||||
}
|
|
||||||
|
|
||||||
var checkMetricsUsage = strings.TrimSpace(`
|
var checkMetricsUsage = strings.TrimSpace(`
|
||||||
Pass Prometheus metrics over stdin to lint them for consistency and correctness.
|
Pass Prometheus metrics over stdin to lint them for consistency and correctness.
|
||||||
|
|
||||||
|
|
|
@ -48,18 +48,6 @@ type Statement interface {
|
||||||
stmt()
|
stmt()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Statements is a list of statement nodes that implements Node.
|
|
||||||
type Statements []Statement
|
|
||||||
|
|
||||||
// AlertStmt represents an added alert rule.
|
|
||||||
type AlertStmt struct {
|
|
||||||
Name string
|
|
||||||
Expr Expr
|
|
||||||
Duration time.Duration
|
|
||||||
Labels labels.Labels
|
|
||||||
Annotations labels.Labels
|
|
||||||
}
|
|
||||||
|
|
||||||
// EvalStmt holds an expression and information on the range it should
|
// EvalStmt holds an expression and information on the range it should
|
||||||
// be evaluated on.
|
// be evaluated on.
|
||||||
type EvalStmt struct {
|
type EvalStmt struct {
|
||||||
|
@ -72,16 +60,7 @@ type EvalStmt struct {
|
||||||
Interval time.Duration
|
Interval time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
// RecordStmt represents an added recording rule.
|
func (*EvalStmt) stmt() {}
|
||||||
type RecordStmt struct {
|
|
||||||
Name string
|
|
||||||
Expr Expr
|
|
||||||
Labels labels.Labels
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*AlertStmt) stmt() {}
|
|
||||||
func (*EvalStmt) stmt() {}
|
|
||||||
func (*RecordStmt) stmt() {}
|
|
||||||
|
|
||||||
// Expr is a generic interface for all expression types.
|
// Expr is a generic interface for all expression types.
|
||||||
type Expr interface {
|
type Expr interface {
|
||||||
|
@ -257,27 +236,11 @@ func Walk(v Visitor, node Node, path []Node) error {
|
||||||
path = append(path, node)
|
path = append(path, node)
|
||||||
|
|
||||||
switch n := node.(type) {
|
switch n := node.(type) {
|
||||||
case Statements:
|
|
||||||
for _, s := range n {
|
|
||||||
if err := Walk(v, s, path); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case *AlertStmt:
|
|
||||||
if err := Walk(v, n.Expr, path); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
case *EvalStmt:
|
case *EvalStmt:
|
||||||
if err := Walk(v, n.Expr, path); err != nil {
|
if err := Walk(v, n.Expr, path); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
case *RecordStmt:
|
|
||||||
if err := Walk(v, n.Expr, path); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
case Expressions:
|
case Expressions:
|
||||||
for _, e := range n {
|
for _, e := range n {
|
||||||
if err := Walk(v, e, path); err != nil {
|
if err := Walk(v, e, path); err != nil {
|
||||||
|
|
|
@ -182,11 +182,6 @@ const (
|
||||||
|
|
||||||
keywordsStart
|
keywordsStart
|
||||||
// Keywords.
|
// Keywords.
|
||||||
itemAlert
|
|
||||||
itemIf
|
|
||||||
itemFor
|
|
||||||
itemLabels
|
|
||||||
itemAnnotations
|
|
||||||
itemOffset
|
itemOffset
|
||||||
itemBy
|
itemBy
|
||||||
itemWithout
|
itemWithout
|
||||||
|
@ -218,11 +213,6 @@ var key = map[string]ItemType{
|
||||||
"quantile": itemQuantile,
|
"quantile": itemQuantile,
|
||||||
|
|
||||||
// Keywords.
|
// Keywords.
|
||||||
"alert": itemAlert,
|
|
||||||
"if": itemIf,
|
|
||||||
"for": itemFor,
|
|
||||||
"labels": itemLabels,
|
|
||||||
"annotations": itemAnnotations,
|
|
||||||
"offset": itemOffset,
|
"offset": itemOffset,
|
||||||
"by": itemBy,
|
"by": itemBy,
|
||||||
"without": itemWithout,
|
"without": itemWithout,
|
||||||
|
|
|
@ -249,21 +249,6 @@ var tests = []struct {
|
||||||
},
|
},
|
||||||
// Test keywords.
|
// Test keywords.
|
||||||
{
|
{
|
||||||
input: "alert",
|
|
||||||
expected: []item{{itemAlert, 0, "alert"}},
|
|
||||||
}, {
|
|
||||||
input: "if",
|
|
||||||
expected: []item{{itemIf, 0, "if"}},
|
|
||||||
}, {
|
|
||||||
input: "for",
|
|
||||||
expected: []item{{itemFor, 0, "for"}},
|
|
||||||
}, {
|
|
||||||
input: "labels",
|
|
||||||
expected: []item{{itemLabels, 0, "labels"}},
|
|
||||||
}, {
|
|
||||||
input: "annotations",
|
|
||||||
expected: []item{{itemAnnotations, 0, "annotations"}},
|
|
||||||
}, {
|
|
||||||
input: "offset",
|
input: "offset",
|
||||||
expected: []item{{itemOffset, 0, "offset"}},
|
expected: []item{{itemOffset, 0, "offset"}},
|
||||||
}, {
|
}, {
|
||||||
|
|
130
promql/parse.go
130
promql/parse.go
|
@ -51,18 +51,6 @@ func (e *ParseErr) Error() string {
|
||||||
return fmt.Sprintf("parse error at line %d, char %d: %s", e.Line, e.Pos, e.Err)
|
return fmt.Sprintf("parse error at line %d, char %d: %s", e.Line, e.Pos, e.Err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseStmts parses the input and returns the resulting statements or any occurring error.
|
|
||||||
func ParseStmts(input string) (Statements, error) {
|
|
||||||
p := newParser(input)
|
|
||||||
|
|
||||||
stmts, err := p.parseStmts()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = p.typecheck(stmts)
|
|
||||||
return stmts, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseExpr returns the expression parsed from the input.
|
// ParseExpr returns the expression parsed from the input.
|
||||||
func ParseExpr(input string) (Expr, error) {
|
func ParseExpr(input string) (Expr, error) {
|
||||||
p := newParser(input)
|
p := newParser(input)
|
||||||
|
@ -112,20 +100,6 @@ func newParser(input string) *parser {
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseStmts parses a sequence of statements from the input.
|
|
||||||
func (p *parser) parseStmts() (stmts Statements, err error) {
|
|
||||||
defer p.recover(&err)
|
|
||||||
stmts = Statements{}
|
|
||||||
|
|
||||||
for p.peek().typ != itemEOF {
|
|
||||||
if p.peek().typ == itemComment {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
stmts = append(stmts, p.stmt())
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseExpr parses a single expression from the input.
|
// parseExpr parses a single expression from the input.
|
||||||
func (p *parser) parseExpr() (expr Expr, err error) {
|
func (p *parser) parseExpr() (expr Expr, err error) {
|
||||||
defer p.recover(&err)
|
defer p.recover(&err)
|
||||||
|
@ -350,93 +324,6 @@ func (p *parser) recover(errp *error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// stmt parses any statement.
|
|
||||||
//
|
|
||||||
// alertStatement | recordStatement
|
|
||||||
//
|
|
||||||
func (p *parser) stmt() Statement {
|
|
||||||
switch tok := p.peek(); tok.typ {
|
|
||||||
case itemAlert:
|
|
||||||
return p.alertStmt()
|
|
||||||
case itemIdentifier, itemMetricIdentifier:
|
|
||||||
return p.recordStmt()
|
|
||||||
}
|
|
||||||
p.errorf("no valid statement detected")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// alertStmt parses an alert rule.
|
|
||||||
//
|
|
||||||
// ALERT name IF expr [FOR duration]
|
|
||||||
// [LABELS label_set]
|
|
||||||
// [ANNOTATIONS label_set]
|
|
||||||
//
|
|
||||||
func (p *parser) alertStmt() *AlertStmt {
|
|
||||||
const ctx = "alert statement"
|
|
||||||
|
|
||||||
p.expect(itemAlert, ctx)
|
|
||||||
name := p.expect(itemIdentifier, ctx)
|
|
||||||
// Alerts require a Vector typed expression.
|
|
||||||
p.expect(itemIf, ctx)
|
|
||||||
expr := p.expr()
|
|
||||||
|
|
||||||
// Optional for clause.
|
|
||||||
var (
|
|
||||||
duration time.Duration
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
if p.peek().typ == itemFor {
|
|
||||||
p.next()
|
|
||||||
dur := p.expect(itemDuration, ctx)
|
|
||||||
duration, err = parseDuration(dur.val)
|
|
||||||
if err != nil {
|
|
||||||
p.error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
lset labels.Labels
|
|
||||||
annotations labels.Labels
|
|
||||||
)
|
|
||||||
if p.peek().typ == itemLabels {
|
|
||||||
p.expect(itemLabels, ctx)
|
|
||||||
lset = p.labelSet()
|
|
||||||
}
|
|
||||||
if p.peek().typ == itemAnnotations {
|
|
||||||
p.expect(itemAnnotations, ctx)
|
|
||||||
annotations = p.labelSet()
|
|
||||||
}
|
|
||||||
|
|
||||||
return &AlertStmt{
|
|
||||||
Name: name.val,
|
|
||||||
Expr: expr,
|
|
||||||
Duration: duration,
|
|
||||||
Labels: lset,
|
|
||||||
Annotations: annotations,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// recordStmt parses a recording rule.
|
|
||||||
func (p *parser) recordStmt() *RecordStmt {
|
|
||||||
const ctx = "record statement"
|
|
||||||
|
|
||||||
name := p.expectOneOf(itemIdentifier, itemMetricIdentifier, ctx).val
|
|
||||||
|
|
||||||
var lset labels.Labels
|
|
||||||
if p.peek().typ == itemLeftBrace {
|
|
||||||
lset = p.labelSet()
|
|
||||||
}
|
|
||||||
|
|
||||||
p.expect(itemAssign, ctx)
|
|
||||||
expr := p.expr()
|
|
||||||
|
|
||||||
return &RecordStmt{
|
|
||||||
Name: name,
|
|
||||||
Labels: lset,
|
|
||||||
Expr: expr,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// expr parses any expression.
|
// expr parses any expression.
|
||||||
func (p *parser) expr() Expr {
|
func (p *parser) expr() Expr {
|
||||||
// Parse the starting expression.
|
// Parse the starting expression.
|
||||||
|
@ -994,9 +881,9 @@ func (p *parser) expectType(node Node, want ValueType, context string) {
|
||||||
// them, but the costs are small and might reveal errors when making changes.
|
// them, but the costs are small and might reveal errors when making changes.
|
||||||
func (p *parser) checkType(node Node) (typ ValueType) {
|
func (p *parser) checkType(node Node) (typ ValueType) {
|
||||||
// For expressions the type is determined by their Type function.
|
// For expressions the type is determined by their Type function.
|
||||||
// Statements and lists do not have a type but are not invalid either.
|
// Lists do not have a type but are not invalid either.
|
||||||
switch n := node.(type) {
|
switch n := node.(type) {
|
||||||
case Statements, Expressions, Statement:
|
case Expressions:
|
||||||
typ = ValueTypeNone
|
typ = ValueTypeNone
|
||||||
case Expr:
|
case Expr:
|
||||||
typ = n.Type()
|
typ = n.Type()
|
||||||
|
@ -1007,25 +894,12 @@ func (p *parser) checkType(node Node) (typ ValueType) {
|
||||||
// Recursively check correct typing for child nodes and raise
|
// Recursively check correct typing for child nodes and raise
|
||||||
// errors in case of bad typing.
|
// errors in case of bad typing.
|
||||||
switch n := node.(type) {
|
switch n := node.(type) {
|
||||||
case Statements:
|
|
||||||
for _, s := range n {
|
|
||||||
p.expectType(s, ValueTypeNone, "statement list")
|
|
||||||
}
|
|
||||||
case *AlertStmt:
|
|
||||||
p.expectType(n.Expr, ValueTypeVector, "alert statement")
|
|
||||||
|
|
||||||
case *EvalStmt:
|
case *EvalStmt:
|
||||||
ty := p.checkType(n.Expr)
|
ty := p.checkType(n.Expr)
|
||||||
if ty == ValueTypeNone {
|
if ty == ValueTypeNone {
|
||||||
p.errorf("evaluation statement must have a valid expression type but got %s", documentedType(ty))
|
p.errorf("evaluation statement must have a valid expression type but got %s", documentedType(ty))
|
||||||
}
|
}
|
||||||
|
|
||||||
case *RecordStmt:
|
|
||||||
ty := p.checkType(n.Expr)
|
|
||||||
if ty != ValueTypeVector && ty != ValueTypeScalar {
|
|
||||||
p.errorf("record statement must have a valid expression of type instant vector or scalar but got %s", documentedType(ty))
|
|
||||||
}
|
|
||||||
|
|
||||||
case Expressions:
|
case Expressions:
|
||||||
for _, e := range n {
|
for _, e := range n {
|
||||||
ty := p.checkType(e)
|
ty := p.checkType(e)
|
||||||
|
|
|
@ -1341,10 +1341,6 @@ var testExpr = []struct {
|
||||||
input: "e-+=/(0)",
|
input: "e-+=/(0)",
|
||||||
fail: true,
|
fail: true,
|
||||||
errMsg: `no valid expression found`,
|
errMsg: `no valid expression found`,
|
||||||
}, {
|
|
||||||
input: "-If",
|
|
||||||
fail: true,
|
|
||||||
errMsg: `no valid expression found`,
|
|
||||||
},
|
},
|
||||||
// String quoting and escape sequence interpretation tests.
|
// String quoting and escape sequence interpretation tests.
|
||||||
{
|
{
|
||||||
|
@ -1445,241 +1441,6 @@ func TestNaNExpression(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var testStatement = []struct {
|
|
||||||
input string
|
|
||||||
expected Statements
|
|
||||||
fail bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
// Test a file-like input.
|
|
||||||
input: `
|
|
||||||
# A simple test recording rule.
|
|
||||||
dc:http_request:rate5m = sum(rate(http_request_count[5m])) by (dc)
|
|
||||||
|
|
||||||
# A simple test alerting rule.
|
|
||||||
ALERT GlobalRequestRateLow IF(dc:http_request:rate5m < 10000) FOR 5m
|
|
||||||
LABELS {
|
|
||||||
service = "testservice"
|
|
||||||
# ... more fields here ...
|
|
||||||
}
|
|
||||||
ANNOTATIONS {
|
|
||||||
summary = "Global request rate low",
|
|
||||||
description = "The global request rate is low"
|
|
||||||
}
|
|
||||||
|
|
||||||
foo = bar{label1="value1"}
|
|
||||||
|
|
||||||
ALERT BazAlert IF foo > 10
|
|
||||||
ANNOTATIONS {
|
|
||||||
description = "BazAlert",
|
|
||||||
runbook = "http://my.url",
|
|
||||||
summary = "Baz",
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
expected: Statements{
|
|
||||||
&RecordStmt{
|
|
||||||
Name: "dc:http_request:rate5m",
|
|
||||||
Expr: &AggregateExpr{
|
|
||||||
Op: itemSum,
|
|
||||||
Grouping: []string{"dc"},
|
|
||||||
Expr: &Call{
|
|
||||||
Func: mustGetFunction("rate"),
|
|
||||||
Args: Expressions{
|
|
||||||
&MatrixSelector{
|
|
||||||
Name: "http_request_count",
|
|
||||||
LabelMatchers: []*labels.Matcher{
|
|
||||||
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "http_request_count"),
|
|
||||||
},
|
|
||||||
Range: 5 * time.Minute,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Labels: nil,
|
|
||||||
},
|
|
||||||
&AlertStmt{
|
|
||||||
Name: "GlobalRequestRateLow",
|
|
||||||
Expr: &ParenExpr{&BinaryExpr{
|
|
||||||
Op: itemLSS,
|
|
||||||
LHS: &VectorSelector{
|
|
||||||
Name: "dc:http_request:rate5m",
|
|
||||||
LabelMatchers: []*labels.Matcher{
|
|
||||||
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "dc:http_request:rate5m"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
RHS: &NumberLiteral{10000},
|
|
||||||
}},
|
|
||||||
Labels: labels.FromStrings("service", "testservice"),
|
|
||||||
Duration: 5 * time.Minute,
|
|
||||||
Annotations: labels.FromStrings(
|
|
||||||
"summary", "Global request rate low",
|
|
||||||
"description", "The global request rate is low",
|
|
||||||
),
|
|
||||||
},
|
|
||||||
&RecordStmt{
|
|
||||||
Name: "foo",
|
|
||||||
Expr: &VectorSelector{
|
|
||||||
Name: "bar",
|
|
||||||
LabelMatchers: []*labels.Matcher{
|
|
||||||
mustLabelMatcher(labels.MatchEqual, "label1", "value1"),
|
|
||||||
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "bar"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
&AlertStmt{
|
|
||||||
Name: "BazAlert",
|
|
||||||
Expr: &BinaryExpr{
|
|
||||||
Op: itemGTR,
|
|
||||||
LHS: &VectorSelector{
|
|
||||||
Name: "foo",
|
|
||||||
LabelMatchers: []*labels.Matcher{
|
|
||||||
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "foo"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
RHS: &NumberLiteral{10},
|
|
||||||
},
|
|
||||||
Annotations: labels.FromStrings(
|
|
||||||
"summary", "Baz",
|
|
||||||
"description", "BazAlert",
|
|
||||||
"runbook", "http://my.url",
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
input: `foo{x="", a="z"} = bar{a="b", x=~"y"}`,
|
|
||||||
expected: Statements{
|
|
||||||
&RecordStmt{
|
|
||||||
Name: "foo",
|
|
||||||
Expr: &VectorSelector{
|
|
||||||
Name: "bar",
|
|
||||||
LabelMatchers: []*labels.Matcher{
|
|
||||||
mustLabelMatcher(labels.MatchEqual, "a", "b"),
|
|
||||||
mustLabelMatcher(labels.MatchRegexp, "x", "y"),
|
|
||||||
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "bar"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Labels: labels.FromStrings("x", "", "a", "z"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
input: `ALERT SomeName IF some_metric > 1
|
|
||||||
LABELS {}
|
|
||||||
ANNOTATIONS {
|
|
||||||
summary = "Global request rate low",
|
|
||||||
description = "The global request rate is low",
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
expected: Statements{
|
|
||||||
&AlertStmt{
|
|
||||||
Name: "SomeName",
|
|
||||||
Expr: &BinaryExpr{
|
|
||||||
Op: itemGTR,
|
|
||||||
LHS: &VectorSelector{
|
|
||||||
Name: "some_metric",
|
|
||||||
LabelMatchers: []*labels.Matcher{
|
|
||||||
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "some_metric"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
RHS: &NumberLiteral{1},
|
|
||||||
},
|
|
||||||
Labels: labels.Labels{},
|
|
||||||
Annotations: labels.FromStrings(
|
|
||||||
"summary", "Global request rate low",
|
|
||||||
"description", "The global request rate is low",
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
input: `
|
|
||||||
# A simple test alerting rule.
|
|
||||||
ALERT GlobalRequestRateLow IF(dc:http_request:rate5m < 10000) FOR 5
|
|
||||||
LABELS {
|
|
||||||
service = "testservice"
|
|
||||||
# ... more fields here ...
|
|
||||||
}
|
|
||||||
ANNOTATIONS {
|
|
||||||
summary = "Global request rate low"
|
|
||||||
description = "The global request rate is low"
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
fail: true,
|
|
||||||
}, {
|
|
||||||
input: "",
|
|
||||||
expected: Statements{},
|
|
||||||
}, {
|
|
||||||
input: "foo = time()",
|
|
||||||
expected: Statements{
|
|
||||||
&RecordStmt{
|
|
||||||
Name: "foo",
|
|
||||||
Expr: &Call{Func: mustGetFunction("time")},
|
|
||||||
Labels: nil,
|
|
||||||
}},
|
|
||||||
}, {
|
|
||||||
input: "foo = 1",
|
|
||||||
expected: Statements{
|
|
||||||
&RecordStmt{
|
|
||||||
Name: "foo",
|
|
||||||
Expr: &NumberLiteral{1},
|
|
||||||
Labels: nil,
|
|
||||||
}},
|
|
||||||
}, {
|
|
||||||
input: "foo = bar[5m]",
|
|
||||||
fail: true,
|
|
||||||
}, {
|
|
||||||
input: `foo = "test"`,
|
|
||||||
fail: true,
|
|
||||||
}, {
|
|
||||||
input: `foo = `,
|
|
||||||
fail: true,
|
|
||||||
}, {
|
|
||||||
input: `foo{a!="b"} = bar`,
|
|
||||||
fail: true,
|
|
||||||
}, {
|
|
||||||
input: `foo{a=~"b"} = bar`,
|
|
||||||
fail: true,
|
|
||||||
}, {
|
|
||||||
input: `foo{a!~"b"} = bar`,
|
|
||||||
fail: true,
|
|
||||||
},
|
|
||||||
// Fuzzing regression tests.
|
|
||||||
{
|
|
||||||
input: `I=-/`,
|
|
||||||
fail: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: `I=3E8/-=`,
|
|
||||||
fail: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: `M=-=-0-0`,
|
|
||||||
fail: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseStatements(t *testing.T) {
|
|
||||||
for _, test := range testStatement {
|
|
||||||
stmts, err := ParseStmts(test.input)
|
|
||||||
|
|
||||||
// Unexpected errors are always caused by a bug.
|
|
||||||
if err == errUnexpected {
|
|
||||||
t.Fatalf("unexpected error occurred")
|
|
||||||
}
|
|
||||||
|
|
||||||
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 !reflect.DeepEqual(stmts, test.expected) {
|
|
||||||
t.Errorf("error in input: \n\n%s\n", test.input)
|
|
||||||
t.Fatalf("no match\n\nexpected:\n%s\ngot: \n%s\n", Tree(test.expected), Tree(stmts))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func mustLabelMatcher(mt labels.MatchType, name, val string) *labels.Matcher {
|
func mustLabelMatcher(mt labels.MatchType, name, val string) *labels.Matcher {
|
||||||
m, err := labels.NewMatcher(mt, name, val)
|
m, err := labels.NewMatcher(mt, name, val)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -34,30 +34,14 @@ func tree(node Node, level string) string {
|
||||||
}
|
}
|
||||||
typs := strings.Split(fmt.Sprintf("%T", node), ".")[1]
|
typs := strings.Split(fmt.Sprintf("%T", node), ".")[1]
|
||||||
|
|
||||||
var t string
|
t := fmt.Sprintf("%s |---- %s :: %s\n", level, typs, node)
|
||||||
// Only print the number of statements for readability.
|
|
||||||
if stmts, ok := node.(Statements); ok {
|
|
||||||
t = fmt.Sprintf("%s |---- %s :: %d\n", level, typs, len(stmts))
|
|
||||||
} else {
|
|
||||||
t = fmt.Sprintf("%s |---- %s :: %s\n", level, typs, node)
|
|
||||||
}
|
|
||||||
|
|
||||||
level += " · · ·"
|
level += " · · ·"
|
||||||
|
|
||||||
switch n := node.(type) {
|
switch n := node.(type) {
|
||||||
case Statements:
|
|
||||||
for _, s := range n {
|
|
||||||
t += tree(s, level)
|
|
||||||
}
|
|
||||||
case *AlertStmt:
|
|
||||||
t += tree(n.Expr, level)
|
|
||||||
|
|
||||||
case *EvalStmt:
|
case *EvalStmt:
|
||||||
t += tree(n.Expr, level)
|
t += tree(n.Expr, level)
|
||||||
|
|
||||||
case *RecordStmt:
|
|
||||||
t += tree(n.Expr, level)
|
|
||||||
|
|
||||||
case Expressions:
|
case Expressions:
|
||||||
for _, e := range n {
|
for _, e := range n {
|
||||||
t += tree(e, level)
|
t += tree(e, level)
|
||||||
|
@ -87,41 +71,10 @@ func tree(node Node, level string) string {
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
func (stmts Statements) String() (s string) {
|
|
||||||
if len(stmts) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
for _, stmt := range stmts {
|
|
||||||
s += stmt.String()
|
|
||||||
s += "\n\n"
|
|
||||||
}
|
|
||||||
return s[:len(s)-2]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (node *AlertStmt) String() string {
|
|
||||||
s := fmt.Sprintf("ALERT %s", node.Name)
|
|
||||||
s += fmt.Sprintf("\n\tIF %s", node.Expr)
|
|
||||||
if node.Duration > 0 {
|
|
||||||
s += fmt.Sprintf("\n\tFOR %s", model.Duration(node.Duration))
|
|
||||||
}
|
|
||||||
if len(node.Labels) > 0 {
|
|
||||||
s += fmt.Sprintf("\n\tLABELS %s", node.Labels)
|
|
||||||
}
|
|
||||||
if len(node.Annotations) > 0 {
|
|
||||||
s += fmt.Sprintf("\n\tANNOTATIONS %s", node.Annotations)
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (node *EvalStmt) String() string {
|
func (node *EvalStmt) String() string {
|
||||||
return "EVAL " + node.Expr.String()
|
return "EVAL " + node.Expr.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (node *RecordStmt) String() string {
|
|
||||||
s := fmt.Sprintf("%s%s = %s", node.Name, node.Labels, node.Expr)
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (es Expressions) String() (s string) {
|
func (es Expressions) String() (s string) {
|
||||||
if len(es) == 0 {
|
if len(es) == 0 {
|
||||||
return ""
|
return ""
|
||||||
|
|
|
@ -15,40 +15,8 @@ package promql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/prometheus/prometheus/pkg/labels"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestStatementString(t *testing.T) {
|
|
||||||
in := &AlertStmt{
|
|
||||||
Name: "FooAlert",
|
|
||||||
Expr: &BinaryExpr{
|
|
||||||
Op: itemGTR,
|
|
||||||
LHS: &VectorSelector{
|
|
||||||
Name: "foo",
|
|
||||||
LabelMatchers: []*labels.Matcher{
|
|
||||||
{Type: labels.MatchEqual, Name: labels.MetricName, Value: "bar"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
RHS: &NumberLiteral{10},
|
|
||||||
},
|
|
||||||
Duration: 5 * time.Minute,
|
|
||||||
Labels: labels.FromStrings("foo", "bar"),
|
|
||||||
Annotations: labels.FromStrings("notify", "team-a"),
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := `ALERT FooAlert
|
|
||||||
IF foo > 10
|
|
||||||
FOR 5m
|
|
||||||
LABELS {foo="bar"}
|
|
||||||
ANNOTATIONS {notify="team-a"}`
|
|
||||||
|
|
||||||
if in.String() != expected {
|
|
||||||
t.Fatalf("expected:\n%s\ngot:\n%s\n", expected, in.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestExprString(t *testing.T) {
|
func TestExprString(t *testing.T) {
|
||||||
// A list of valid expressions that are expected to be
|
// A list of valid expressions that are expected to be
|
||||||
// returned as out when calling String(). If out is empty the output
|
// returned as out when calling String(). If out is empty the output
|
||||||
|
@ -129,35 +97,3 @@ func TestExprString(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStmtsString(t *testing.T) {
|
|
||||||
// A list of valid statements that are expected to be returned as out when
|
|
||||||
// calling String(). If out is empty the output is expected to equal the
|
|
||||||
// input.
|
|
||||||
inputs := []struct {
|
|
||||||
in, out string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
in: `ALERT foo IF up == 0 FOR 1m`,
|
|
||||||
out: "ALERT foo\n\tIF up == 0\n\tFOR 1m",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
in: `ALERT foo IF up == 0 FOR 1m ANNOTATIONS {summary="foo"}`,
|
|
||||||
out: "ALERT foo\n\tIF up == 0\n\tFOR 1m\n\tANNOTATIONS {summary=\"foo\"}",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range inputs {
|
|
||||||
expr, err := ParseStmts(test.in)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("parsing error for %q: %s", test.in, err)
|
|
||||||
}
|
|
||||||
exp := test.in
|
|
||||||
if test.out != "" {
|
|
||||||
exp = test.out
|
|
||||||
}
|
|
||||||
if expr.String() != exp {
|
|
||||||
t.Fatalf("expected %q to be returned as:\n%s\ngot:\n%s\n", test.in, exp, expr.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue