Merge pull request #3839 from brancz/remove-old-alert-record

promql: Remove old and unused alerting/reconding syntax
pull/4834/head
Frederic Branczyk 2018-11-06 15:53:27 +01:00 committed by GitHub
commit bda9781ccd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 4 additions and 621 deletions

View File

@ -16,7 +16,6 @@ package main
import (
"context"
"fmt"
"io/ioutil"
"math"
"net/url"
"os"
@ -26,16 +25,13 @@ import (
"time"
"gopkg.in/alecthomas/kingpin.v2"
"gopkg.in/yaml.v2"
"github.com/prometheus/client_golang/api"
"github.com/prometheus/client_golang/api/prometheus/v1"
config_util "github.com/prometheus/common/config"
"github.com/prometheus/common/model"
"github.com/prometheus/common/version"
"github.com/prometheus/prometheus/config"
"github.com/prometheus/prometheus/pkg/rulefmt"
"github.com/prometheus/prometheus/promql"
"github.com/prometheus/prometheus/util/promlint"
)
@ -60,10 +56,6 @@ func main() {
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.")
queryInstantCmd := queryCmd.Command("instant", "Run instant query.")
queryServer := queryInstantCmd.Arg("server", "Prometheus server to query.").Required().String()
@ -111,9 +103,6 @@ func main() {
case checkMetricsCmd.FullCommand():
os.Exit(CheckMetrics())
case updateRulesCmd.FullCommand():
os.Exit(UpdateRules(*ruleFilesUp...))
case queryInstantCmd.FullCommand():
os.Exit(QueryInstant(*queryServer, *queryExpr))
@ -296,74 +285,6 @@ func checkRules(filename string) (int, []error) {
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(`
Pass Prometheus metrics over stdin to lint them for consistency and correctness.

View File

@ -48,18 +48,6 @@ type Statement interface {
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
// be evaluated on.
type EvalStmt struct {
@ -72,16 +60,7 @@ type EvalStmt struct {
Interval time.Duration
}
// RecordStmt represents an added recording rule.
type RecordStmt struct {
Name string
Expr Expr
Labels labels.Labels
}
func (*AlertStmt) stmt() {}
func (*EvalStmt) stmt() {}
func (*RecordStmt) stmt() {}
func (*EvalStmt) stmt() {}
// Expr is a generic interface for all expression types.
type Expr interface {
@ -257,27 +236,11 @@ func Walk(v Visitor, node Node, path []Node) error {
path = append(path, node)
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:
if err := Walk(v, n.Expr, path); err != nil {
return err
}
case *RecordStmt:
if err := Walk(v, n.Expr, path); err != nil {
return err
}
case Expressions:
for _, e := range n {
if err := Walk(v, e, path); err != nil {

View File

@ -183,11 +183,6 @@ const (
keywordsStart
// Keywords.
itemAlert
itemIf
itemFor
itemLabels
itemAnnotations
itemOffset
itemBy
itemWithout
@ -219,11 +214,6 @@ var key = map[string]ItemType{
"quantile": itemQuantile,
// Keywords.
"alert": itemAlert,
"if": itemIf,
"for": itemFor,
"labels": itemLabels,
"annotations": itemAnnotations,
"offset": itemOffset,
"by": itemBy,
"without": itemWithout,

View File

@ -249,21 +249,6 @@ var tests = []struct {
},
// 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",
expected: []item{{itemOffset, 0, "offset"}},
}, {

View File

@ -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)
}
// 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.
func ParseExpr(input string) (Expr, error) {
p := newParser(input)
@ -112,20 +100,6 @@ func newParser(input string) *parser {
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.
func (p *parser) parseExpr() (expr Expr, err error) {
defer p.recover(&err)
@ -365,93 +339,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.
func (p *parser) expr() Expr {
// Parse the starting expression.
@ -1009,9 +896,9 @@ func (p *parser) expectType(node Node, want ValueType, context string) {
// them, but the costs are small and might reveal errors when making changes.
func (p *parser) checkType(node Node) (typ ValueType) {
// 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) {
case Statements, Expressions, Statement:
case Expressions:
typ = ValueTypeNone
case Expr:
typ = n.Type()
@ -1022,25 +909,12 @@ func (p *parser) checkType(node Node) (typ ValueType) {
// Recursively check correct typing for child nodes and raise
// errors in case of bad typing.
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:
ty := p.checkType(n.Expr)
if ty == ValueTypeNone {
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:
for _, e := range n {
ty := p.checkType(e)

View File

@ -1341,10 +1341,6 @@ var testExpr = []struct {
input: "e-+=/(0)",
fail: true,
errMsg: `no valid expression found`,
}, {
input: "-If",
fail: true,
errMsg: `no valid expression found`,
},
// 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 {
m, err := labels.NewMatcher(mt, name, val)
if err != nil {

View File

@ -34,30 +34,14 @@ func tree(node Node, level string) string {
}
typs := strings.Split(fmt.Sprintf("%T", node), ".")[1]
var t string
// 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)
}
t := fmt.Sprintf("%s |---- %s :: %s\n", level, typs, node)
level += " · · ·"
switch n := node.(type) {
case Statements:
for _, s := range n {
t += tree(s, level)
}
case *AlertStmt:
t += tree(n.Expr, level)
case *EvalStmt:
t += tree(n.Expr, level)
case *RecordStmt:
t += tree(n.Expr, level)
case Expressions:
for _, e := range n {
t += tree(e, level)
@ -87,41 +71,10 @@ func tree(node Node, level string) string {
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 {
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) {
if len(es) == 0 {
return ""

View File

@ -15,40 +15,8 @@ package promql
import (
"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) {
// A list of valid expressions that are expected to be
// 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())
}
}
}