Implement initial no-op alert parsing and rule parsing tests.

This commit is contained in:
Julius Volz 2013-04-23 00:26:59 +02:00 committed by Julius Volz
parent 955708e8db
commit c0601abf46
15 changed files with 406 additions and 155 deletions

1
.gitignore vendored
View file

@ -8,7 +8,6 @@
*.orig
*.pyc
*.rej
*.rules
*.so
*~
.*.swp

View file

@ -54,30 +54,30 @@ var configTests = []struct {
}
func TestConfigs(t *testing.T) {
for _, configTest := range configTests {
for i, configTest := range configTests {
testConfig, err := LoadFromFile(path.Join(fixturesPath, configTest.inputFile))
if err != nil {
if !configTest.shouldFail {
t.Errorf("Error parsing config %v: %v", configTest.inputFile, err)
t.Fatalf("%d. Error parsing config %v: %v", i, configTest.inputFile, err)
} else {
if !strings.Contains(err.Error(), configTest.errContains) {
t.Errorf("Expected error containing '%v', got: %v", configTest.errContains, err)
t.Fatalf("%d. Expected error containing '%v', got: %v", i, configTest.errContains, err)
}
}
} else {
printedConfig, err := ioutil.ReadFile(path.Join(fixturesPath, configTest.printedFile))
if err != nil {
t.Errorf("Error reading config %v: %v", configTest.inputFile, err)
t.Fatalf("%d. Error reading config %v: %v", i, configTest.inputFile, err)
continue
}
expected := string(printedConfig)
actual := testConfig.ToString(0)
if actual != expected {
t.Errorf("%v: printed config doesn't match expected output", configTest.inputFile)
t.Errorf("%d. %v: printed config doesn't match expected output", i, configTest.inputFile)
t.Errorf("Expected:\n%v\n\nActual:\n%v\n", expected, actual)
t.Errorf("Writing expected and actual printed configs to /tmp for diffing (see test source for paths)")
t.Fatalf("Writing expected and actual printed configs to /tmp for diffing (see test source for paths)")
ioutil.WriteFile(fmt.Sprintf("/tmp/%s.expected", configTest.printedFile), []byte(expected), 0600)
ioutil.WriteFile(fmt.Sprintf("/tmp/%s.actual", configTest.printedFile), []byte(actual), 0600)
}

View file

View file

@ -0,0 +1,13 @@
// 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 WITH {
description = "Global HTTP request rate low!",
summary = "Request rate low"
/* ... more fields here ... */
}
foo = bar{label1="value1"}
ALERT BazAlert IF(foo > 10) WITH {}

View file

@ -0,0 +1 @@
now = time()

View file

@ -0,0 +1,13 @@
// A simple test recording rule.
dc_http_request_rate5m = sum(rate(http_request_count[5m])) by (dc)
// A simple test alerting rule with a syntax error (invalid duration string "5").
ALERT GlobalRequestRateLow IF(dc_http_request_rate5m < 10000) FOR 5 WITH {
description = "Global HTTP request rate low!",
summary = "Request rate low"
/* ... more fields here ... */
}
foo = bar{label1="value1"}
ALERT BazAlert IF(foo > 10) WITH {}

View file

@ -20,11 +20,22 @@ import (
"github.com/prometheus/prometheus/utility"
)
func CreateRule(name string, labels model.LabelSet, root ast.Node, permanent bool) (*Rule, error) {
if root.Type() != ast.VECTOR {
return nil, fmt.Errorf("Rule %v does not evaluate to vector type", name)
func CreateRecordingRule(name string, labels model.LabelSet, expr ast.Node, permanent bool) (*RecordingRule, error) {
if _, ok := expr.(ast.VectorNode); !ok {
return nil, fmt.Errorf("Recording rule expression %v does not evaluate to vector type", expr)
}
return NewRule(name, labels, root.(ast.VectorNode), permanent), nil
return NewRecordingRule(name, labels, expr.(ast.VectorNode), permanent), nil
}
func CreateAlertingRule(name string, expr ast.Node, holdDurationStr string, labels model.LabelSet) (*AlertingRule, error) {
if _, ok := expr.(ast.VectorNode); !ok {
return nil, fmt.Errorf("Alert rule expression %v does not evaluate to vector type", expr)
}
holdDuration, err := utility.StringToDuration(holdDurationStr)
if err != nil {
return nil, err
}
return NewAlertingRule(name, expr.(ast.VectorNode), holdDuration, labels), nil
}
func NewFunctionCall(name string, args []ast.Node) (ast.Node, error) {
@ -40,7 +51,7 @@ func NewFunctionCall(name string, args []ast.Node) (ast.Node, error) {
}
func NewVectorAggregation(aggrTypeStr string, vector ast.Node, groupBy []model.LabelName) (*ast.VectorAggregation, error) {
if vector.Type() != ast.VECTOR {
if _, ok := vector.(ast.VectorNode); !ok {
return nil, fmt.Errorf("Operand of %v aggregation must be of vector type", aggrTypeStr)
}
var aggrTypes = map[string]ast.AggrType{

View file

@ -37,11 +37,16 @@ U [smhdwy]
\/\/[^\r\n]*\n { /* gobble up one-line comments */ }
permanent { return PERMANENT }
ALERT|alert { return ALERT }
IF|if { return IF }
FOR|for { return FOR }
WITH|with { return WITH }
PERMANENT|permanent { return PERMANENT }
BY|by { return GROUP_OP }
AVG|SUM|MAX|MIN { yylval.str = yytext; return AGGR_OP }
avg|sum|max|min { yylval.str = strings.ToUpper(yytext); return AGGR_OP }
\<|>|AND|OR { yylval.str = yytext; return CMP_OP }
\<|>|AND|OR|and|or { yylval.str = strings.ToUpper(yytext); return CMP_OP }
==|!=|>=|<= { yylval.str = yytext; return CMP_OP }
[+\-] { yylval.str = yytext; return ADDITIVE_OP }
[*/%] { yylval.str = yytext; return MULT_OP }
@ -49,7 +54,7 @@ avg|sum|max|min { yylval.str = strings.ToUpper(yytext); return AGGR_OP
{D}+{U} { yylval.str = yytext; return DURATION }
{L}({L}|{D})* { yylval.str = yytext; return IDENTIFIER }
\-?{D}+(\.{D}*)? { num, err := strconv.ParseFloat(yytext, 32);
\-?{D}+(\.{D}*)? { num, err := strconv.ParseFloat(yytext, 64);
if (err != nil && err.(*strconv.NumError).Err == strconv.ErrSyntax) {
panic("Invalid float")
}

View file

@ -302,7 +302,59 @@ var yyrules []yyrule = []yyrule{{regexp.MustCompile("[^\\n]"), nil, []yystartcon
{
}
return yyactionreturn{0, yyRT_FALLTHROUGH}
}}, {regexp.MustCompile("permanent"), nil, []yystartcondition{}, false, func() (yyar yyactionreturn) {
}}, {regexp.MustCompile("ALERT|alert"), nil, []yystartcondition{}, false, func() (yyar yyactionreturn) {
defer func() {
if r := recover(); r != nil {
if r != "yyREJECT" {
panic(r)
}
yyar.returnType = yyRT_REJECT
}
}()
{
return yyactionreturn{ALERT, yyRT_USER_RETURN}
}
return yyactionreturn{0, yyRT_FALLTHROUGH}
}}, {regexp.MustCompile("IF|if"), nil, []yystartcondition{}, false, func() (yyar yyactionreturn) {
defer func() {
if r := recover(); r != nil {
if r != "yyREJECT" {
panic(r)
}
yyar.returnType = yyRT_REJECT
}
}()
{
return yyactionreturn{IF, yyRT_USER_RETURN}
}
return yyactionreturn{0, yyRT_FALLTHROUGH}
}}, {regexp.MustCompile("FOR|for"), nil, []yystartcondition{}, false, func() (yyar yyactionreturn) {
defer func() {
if r := recover(); r != nil {
if r != "yyREJECT" {
panic(r)
}
yyar.returnType = yyRT_REJECT
}
}()
{
return yyactionreturn{FOR, yyRT_USER_RETURN}
}
return yyactionreturn{0, yyRT_FALLTHROUGH}
}}, {regexp.MustCompile("WITH|with"), nil, []yystartcondition{}, false, func() (yyar yyactionreturn) {
defer func() {
if r := recover(); r != nil {
if r != "yyREJECT" {
panic(r)
}
yyar.returnType = yyRT_REJECT
}
}()
{
return yyactionreturn{WITH, yyRT_USER_RETURN}
}
return yyactionreturn{0, yyRT_FALLTHROUGH}
}}, {regexp.MustCompile("PERMANENT|permanent"), nil, []yystartcondition{}, false, func() (yyar yyactionreturn) {
defer func() {
if r := recover(); r != nil {
if r != "yyREJECT" {
@ -356,7 +408,7 @@ var yyrules []yyrule = []yyrule{{regexp.MustCompile("[^\\n]"), nil, []yystartcon
return yyactionreturn{AGGR_OP, yyRT_USER_RETURN}
}
return yyactionreturn{0, yyRT_FALLTHROUGH}
}}, {regexp.MustCompile("\\<|>|AND|OR"), nil, []yystartcondition{}, false, func() (yyar yyactionreturn) {
}}, {regexp.MustCompile("\\<|>|AND|OR|and|or"), nil, []yystartcondition{}, false, func() (yyar yyactionreturn) {
defer func() {
if r := recover(); r != nil {
if r != "yyREJECT" {
@ -366,7 +418,7 @@ var yyrules []yyrule = []yyrule{{regexp.MustCompile("[^\\n]"), nil, []yystartcon
}
}()
{
yylval.str = yytext
yylval.str = strings.ToUpper(yytext)
return yyactionreturn{CMP_OP, yyRT_USER_RETURN}
}
return yyactionreturn{0, yyRT_FALLTHROUGH}
@ -450,7 +502,7 @@ var yyrules []yyrule = []yyrule{{regexp.MustCompile("[^\\n]"), nil, []yystartcon
}
}()
{
num, err := strconv.ParseFloat(yytext, 32)
num, err := strconv.ParseFloat(yytext, 64)
if err != nil && err.(*strconv.NumError).Err == strconv.ErrSyntax {
panic("Invalid float")
}

View file

@ -34,7 +34,7 @@ var (
type RulesLexer struct {
errors []string // Errors encountered during parsing.
startToken int // Dummy token to simulate multiple start symbols (see below).
parsedRules []*Rule // Parsed full rules.
parsedRules []Rule // Parsed full rules.
parsedExpr ast.Node // Parsed single expression.
}
@ -95,23 +95,23 @@ func LoadFromReader(rulesReader io.Reader, singleExpr bool) (interface{}, error)
panic("")
}
func LoadRulesFromReader(rulesReader io.Reader) ([]*Rule, error) {
func LoadRulesFromReader(rulesReader io.Reader) ([]Rule, error) {
expr, err := LoadFromReader(rulesReader, false)
if err != nil {
return nil, err
}
return expr.([]*Rule), err
return expr.([]Rule), err
}
func LoadRulesFromString(rulesString string) ([]*Rule, error) {
func LoadRulesFromString(rulesString string) ([]Rule, error) {
rulesReader := strings.NewReader(rulesString)
return LoadRulesFromReader(rulesReader)
}
func LoadRulesFromFile(fileName string) ([]*Rule, error) {
func LoadRulesFromFile(fileName string) ([]Rule, error) {
rulesReader, err := os.Open(fileName)
if err != nil {
return []*Rule{}, err
return []Rule{}, err
}
defer rulesReader.Close()
return LoadRulesFromReader(rulesReader)

View file

@ -31,7 +31,7 @@ type RuleManager interface {
}
type ruleManager struct {
rules []*Rule
rules []Rule
results chan *Result
done chan bool
interval time.Duration
@ -40,7 +40,8 @@ type ruleManager struct {
func NewRuleManager(results chan *Result, interval time.Duration) RuleManager {
manager := &ruleManager{
results: results,
rules: []*Rule{},
rules: []Rule{},
done: make(chan bool),
interval: interval,
}
go manager.run(results)
@ -70,7 +71,7 @@ func (m *ruleManager) runIteration(results chan *Result) {
wg := sync.WaitGroup{}
for _, rule := range m.rules {
wg.Add(1)
go func(rule *Rule) {
go func(rule Rule) {
vector, err := rule.Eval(&now)
m.results <- &Result{
Samples: vector,

View file

@ -39,12 +39,14 @@
%token <num> NUMBER
%token PERMANENT GROUP_OP
%token <str> AGGR_OP CMP_OP ADDITIVE_OP MULT_OP
%token ALERT IF FOR WITH
%type <ruleNodeSlice> func_arg_list
%type <labelNameSlice> label_list grouping_opts
%type <labelSet> label_assign label_assign_list rule_labels
%type <ruleNode> rule_expr func_arg
%type <boolean> qualifier
%type <str> for_duration
%right '='
%left CMP_OP
@ -67,10 +69,22 @@ saved_rule_expr : rule_expr
rules_stat : qualifier IDENTIFIER rule_labels '=' rule_expr
{
rule, err := CreateRule($2, $3, $5, $1)
rule, err := CreateRecordingRule($2, $3, $5, $1)
if err != nil { yylex.Error(err.Error()); return 1 }
yylex.(*RulesLexer).parsedRules = append(yylex.(*RulesLexer).parsedRules, rule)
}
| ALERT IDENTIFIER IF rule_expr for_duration WITH rule_labels
{
rule, err := CreateAlertingRule($2, $4, $5, $7)
if err != nil { yylex.Error(err.Error()); return 1 }
yylex.(*RulesLexer).parsedRules = append(yylex.(*RulesLexer).parsedRules, rule)
}
;
for_duration : /* empty */
{ $$ = "0s" }
| FOR DURATION
{ $$ = $2 }
;
qualifier : /* empty */

View file

@ -30,6 +30,10 @@ const AGGR_OP = 57354
const CMP_OP = 57355
const ADDITIVE_OP = 57356
const MULT_OP = 57357
const ALERT = 57358
const IF = 57359
const FOR = 57360
const WITH = 57361
var yyToknames = []string{
"START_RULES",
@ -44,6 +48,10 @@ var yyToknames = []string{
"CMP_OP",
"ADDITIVE_OP",
"MULT_OP",
"ALERT",
"IF",
"FOR",
"WITH",
" =",
}
var yyStatenames = []string{}
@ -52,7 +60,7 @@ const yyEofCode = 1
const yyErrCode = 2
const yyMaxDepth = 200
//line parser.y:175
//line parser.y:189
//line yacctab:1
@ -61,75 +69,79 @@ var yyExca = []int{
1, -1,
-2, 0,
-1, 4,
6, 7,
6, 10,
-2, 1,
}
const yyNprod = 33
const yyNprod = 36
const yyPrivate = 57344
var yyTokenNames []string
var yyStates []string
const yyLast = 84
const yyLast = 97
var yyAct = []int{
32, 36, 31, 16, 6, 17, 15, 16, 18, 40,
14, 14, 21, 46, 14, 20, 25, 26, 27, 8,
33, 54, 10, 38, 22, 9, 19, 17, 15, 16,
17, 15, 16, 7, 30, 28, 14, 8, 33, 14,
10, 15, 16, 9, 47, 48, 49, 21, 53, 14,
39, 7, 8, 37, 58, 10, 57, 42, 9, 41,
43, 44, 52, 13, 45, 35, 7, 24, 50, 59,
56, 37, 23, 2, 3, 11, 5, 4, 1, 12,
34, 51, 55, 29,
20, 38, 34, 17, 33, 43, 6, 67, 15, 66,
19, 18, 16, 17, 15, 45, 59, 44, 60, 27,
28, 29, 16, 17, 15, 41, 40, 18, 16, 17,
18, 16, 17, 22, 15, 23, 21, 46, 47, 49,
15, 8, 35, 15, 10, 51, 22, 9, 50, 53,
52, 48, 61, 57, 18, 16, 17, 39, 8, 7,
32, 10, 65, 42, 9, 56, 30, 15, 8, 35,
54, 10, 62, 37, 9, 14, 7, 26, 68, 64,
39, 13, 25, 24, 2, 3, 7, 11, 5, 4,
1, 58, 12, 36, 55, 63, 31,
}
var yyPact = []int{
69, -1000, -1000, 46, 53, -1000, 17, 46, -5, 4,
-1000, -1000, 66, -1000, 59, 46, 46, 46, 14, -1000,
13, 47, 46, 30, -14, -12, -11, 27, -1000, 38,
-1000, -1000, 17, -1000, 42, -1000, -1000, 48, -8, 28,
-1000, -1000, 31, -1000, 65, 61, 51, 46, -1000, -1000,
-1000, -1000, 1, 17, 64, 35, -1000, -1000, 63, -1000,
80, -1000, -1000, 52, 65, -1000, 17, 52, 12, 11,
-1000, -1000, 77, 76, -1000, 69, 52, 52, 52, 41,
-1000, 35, 51, 52, 25, 46, -22, -12, -18, 8,
-1000, -8, -1000, -1000, 17, -1000, 15, -1000, -1000, 31,
14, 28, 52, -1000, -1000, 62, -1000, 74, 63, 54,
52, -2, -1000, -1000, -1000, -1000, -6, 17, 33, 64,
73, 25, -1000, -16, -1000, -1000, -1000, 72, -1000,
}
var yyPgo = []int{
0, 83, 82, 81, 1, 80, 26, 0, 2, 79,
78, 77, 76, 75,
0, 96, 95, 94, 1, 93, 0, 2, 4, 92,
91, 90, 89, 88, 87,
}
var yyR1 = []int{
0, 10, 10, 11, 11, 12, 13, 9, 9, 6,
6, 6, 5, 5, 4, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 3, 3, 2, 2, 1,
1, 8, 8,
0, 11, 11, 12, 12, 13, 14, 14, 10, 10,
9, 9, 6, 6, 6, 5, 5, 4, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 3, 3,
2, 2, 1, 1, 8, 8,
}
var yyR2 = []int{
0, 2, 2, 0, 2, 1, 5, 0, 1, 0,
3, 2, 1, 3, 3, 3, 2, 4, 3, 4,
5, 3, 3, 3, 1, 0, 4, 1, 3, 1,
3, 1, 1,
0, 2, 2, 0, 2, 1, 5, 7, 0, 2,
0, 1, 0, 3, 2, 1, 3, 3, 3, 2,
4, 3, 4, 5, 3, 3, 3, 1, 0, 4,
1, 3, 1, 3, 1, 1,
}
var yyChk = []int{
-1000, -10, 4, 5, -11, -12, -7, 20, 6, 12,
9, -13, -9, 10, 22, 14, 15, 13, -7, -6,
20, 17, 20, 6, 8, -7, -7, -7, 21, -1,
21, -8, -7, 7, -5, 18, -4, 6, -7, -6,
23, 21, 19, 18, 19, 16, 21, 16, -8, -4,
7, -3, 11, -7, 20, -2, 6, 21, 19, 6,
-1000, -11, 4, 5, -12, -13, -7, 24, 6, 12,
9, -14, -9, 16, 10, 26, 14, 15, 13, -7,
-6, 24, 21, 24, 6, 6, 8, -7, -7, -7,
25, -1, 25, -8, -7, 7, -5, 22, -4, 6,
-7, -6, 17, 27, 25, 23, 22, 23, 20, 25,
20, -7, -8, -4, 7, -3, 11, -7, -10, 18,
24, 19, 8, -2, 6, -6, 25, 23, 6,
}
var yyDef = []int{
0, -2, 3, 0, -2, 2, 5, 0, 9, 0,
24, 4, 0, 8, 0, 0, 0, 0, 0, 16,
0, 0, 0, 9, 0, 21, 22, 23, 15, 0,
18, 29, 31, 32, 0, 11, 12, 0, 0, 0,
19, 17, 0, 10, 0, 0, 25, 0, 30, 13,
14, 20, 0, 6, 0, 0, 27, 26, 0, 28,
0, -2, 3, 0, -2, 2, 5, 0, 12, 0,
27, 4, 0, 0, 11, 0, 0, 0, 0, 0,
19, 0, 0, 0, 12, 0, 0, 24, 25, 26,
18, 0, 21, 32, 34, 35, 0, 14, 15, 0,
0, 0, 0, 22, 20, 0, 13, 0, 0, 28,
0, 8, 33, 16, 17, 23, 0, 6, 0, 0,
0, 12, 9, 0, 30, 7, 29, 0, 31,
}
var yyTok1 = []int{
@ -137,20 +149,20 @@ var yyTok1 = []int{
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
20, 21, 3, 3, 19, 3, 3, 3, 3, 3,
24, 25, 3, 3, 23, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 16, 3, 3, 3, 3, 3, 3, 3, 3,
3, 20, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 22, 3, 23, 3, 3, 3, 3, 3, 3,
3, 26, 3, 27, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 17, 3, 18,
3, 3, 3, 21, 3, 22,
}
var yyTok2 = []int{
2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
12, 13, 14, 15,
12, 13, 14, 15, 16, 17, 18, 19,
}
var yyTok3 = []int{
0,
@ -381,120 +393,133 @@ yydefault:
switch yynt {
case 5:
//line parser.y:65
//line parser.y:67
{ yylex.(*RulesLexer).parsedExpr = yyS[yypt-0].ruleNode }
case 6:
//line parser.y:69
//line parser.y:71
{
rule, err := CreateRule(yyS[yypt-3].str, yyS[yypt-2].labelSet, yyS[yypt-0].ruleNode, yyS[yypt-4].boolean)
rule, err := CreateRecordingRule(yyS[yypt-3].str, yyS[yypt-2].labelSet, yyS[yypt-0].ruleNode, yyS[yypt-4].boolean)
if err != nil { yylex.Error(err.Error()); return 1 }
yylex.(*RulesLexer).parsedRules = append(yylex.(*RulesLexer).parsedRules, rule)
}
case 7:
//line parser.y:77
{ yyVAL.boolean = false }
{
rule, err := CreateAlertingRule(yyS[yypt-5].str, yyS[yypt-3].ruleNode, yyS[yypt-2].str, yyS[yypt-0].labelSet)
if err != nil { yylex.Error(err.Error()); return 1 }
yylex.(*RulesLexer).parsedRules = append(yylex.(*RulesLexer).parsedRules, rule)
}
case 8:
//line parser.y:79
{ yyVAL.boolean = true }
case 9:
//line parser.y:83
{ yyVAL.labelSet = model.LabelSet{} }
case 10:
//line parser.y:85
{ yyVAL.labelSet = yyS[yypt-1].labelSet }
case 11:
{ yyVAL.str = "0s" }
case 9:
//line parser.y:87
{ yyVAL.labelSet = model.LabelSet{} }
{ yyVAL.str = yyS[yypt-0].str }
case 10:
//line parser.y:91
{ yyVAL.boolean = false }
case 11:
//line parser.y:93
{ yyVAL.boolean = true }
case 12:
//line parser.y:90
{ yyVAL.labelSet = yyS[yypt-0].labelSet }
//line parser.y:97
{ yyVAL.labelSet = model.LabelSet{} }
case 13:
//line parser.y:92
{ for k, v := range yyS[yypt-0].labelSet { yyVAL.labelSet[k] = v } }
//line parser.y:99
{ yyVAL.labelSet = yyS[yypt-1].labelSet }
case 14:
//line parser.y:96
{ yyVAL.labelSet = model.LabelSet{ model.LabelName(yyS[yypt-2].str): model.LabelValue(yyS[yypt-0].str) } }
case 15:
//line parser.y:101
{ yyVAL.ruleNode = yyS[yypt-1].ruleNode }
{ yyVAL.labelSet = model.LabelSet{} }
case 15:
//line parser.y:104
{ yyVAL.labelSet = yyS[yypt-0].labelSet }
case 16:
//line parser.y:103
{ yyS[yypt-0].labelSet[model.MetricNameLabel] = model.LabelValue(yyS[yypt-1].str); yyVAL.ruleNode = ast.NewVectorLiteral(yyS[yypt-0].labelSet) }
//line parser.y:106
{ for k, v := range yyS[yypt-0].labelSet { yyVAL.labelSet[k] = v } }
case 17:
//line parser.y:105
//line parser.y:110
{ yyVAL.labelSet = model.LabelSet{ model.LabelName(yyS[yypt-2].str): model.LabelValue(yyS[yypt-0].str) } }
case 18:
//line parser.y:115
{ yyVAL.ruleNode = yyS[yypt-1].ruleNode }
case 19:
//line parser.y:117
{ yyS[yypt-0].labelSet[model.MetricNameLabel] = model.LabelValue(yyS[yypt-1].str); yyVAL.ruleNode = ast.NewVectorLiteral(yyS[yypt-0].labelSet) }
case 20:
//line parser.y:119
{
var err error
yyVAL.ruleNode, err = NewFunctionCall(yyS[yypt-3].str, yyS[yypt-1].ruleNodeSlice)
if err != nil { yylex.Error(err.Error()); return 1 }
}
case 18:
//line parser.y:111
case 21:
//line parser.y:125
{
var err error
yyVAL.ruleNode, err = NewFunctionCall(yyS[yypt-2].str, []ast.Node{})
if err != nil { yylex.Error(err.Error()); return 1 }
}
case 19:
//line parser.y:117
case 22:
//line parser.y:131
{
var err error
yyVAL.ruleNode, err = NewMatrix(yyS[yypt-3].ruleNode, yyS[yypt-1].str)
if err != nil { yylex.Error(err.Error()); return 1 }
}
case 20:
//line parser.y:123
case 23:
//line parser.y:137
{
var err error
yyVAL.ruleNode, err = NewVectorAggregation(yyS[yypt-4].str, yyS[yypt-2].ruleNode, yyS[yypt-0].labelNameSlice)
if err != nil { yylex.Error(err.Error()); return 1 }
}
case 21:
//line parser.y:131
{
var err error
yyVAL.ruleNode, err = NewArithExpr(yyS[yypt-1].str, yyS[yypt-2].ruleNode, yyS[yypt-0].ruleNode)
if err != nil { yylex.Error(err.Error()); return 1 }
}
case 22:
//line parser.y:137
{
var err error
yyVAL.ruleNode, err = NewArithExpr(yyS[yypt-1].str, yyS[yypt-2].ruleNode, yyS[yypt-0].ruleNode)
if err != nil { yylex.Error(err.Error()); return 1 }
}
case 23:
//line parser.y:143
{
var err error
yyVAL.ruleNode, err = NewArithExpr(yyS[yypt-1].str, yyS[yypt-2].ruleNode, yyS[yypt-0].ruleNode)
if err != nil { yylex.Error(err.Error()); return 1 }
}
case 24:
//line parser.y:149
{ yyVAL.ruleNode = ast.NewScalarLiteral(yyS[yypt-0].num)}
//line parser.y:145
{
var err error
yyVAL.ruleNode, err = NewArithExpr(yyS[yypt-1].str, yyS[yypt-2].ruleNode, yyS[yypt-0].ruleNode)
if err != nil { yylex.Error(err.Error()); return 1 }
}
case 25:
//line parser.y:153
{ yyVAL.labelNameSlice = []model.LabelName{} }
//line parser.y:151
{
var err error
yyVAL.ruleNode, err = NewArithExpr(yyS[yypt-1].str, yyS[yypt-2].ruleNode, yyS[yypt-0].ruleNode)
if err != nil { yylex.Error(err.Error()); return 1 }
}
case 26:
//line parser.y:155
{ yyVAL.labelNameSlice = yyS[yypt-1].labelNameSlice }
//line parser.y:157
{
var err error
yyVAL.ruleNode, err = NewArithExpr(yyS[yypt-1].str, yyS[yypt-2].ruleNode, yyS[yypt-0].ruleNode)
if err != nil { yylex.Error(err.Error()); return 1 }
}
case 27:
//line parser.y:159
{ yyVAL.labelNameSlice = []model.LabelName{model.LabelName(yyS[yypt-0].str)} }
//line parser.y:163
{ yyVAL.ruleNode = ast.NewScalarLiteral(yyS[yypt-0].num)}
case 28:
//line parser.y:161
{ yyVAL.labelNameSlice = append(yyVAL.labelNameSlice, model.LabelName(yyS[yypt-0].str)) }
case 29:
//line parser.y:165
{ yyVAL.ruleNodeSlice = []ast.Node{yyS[yypt-0].ruleNode} }
case 30:
//line parser.y:167
{ yyVAL.ruleNodeSlice = append(yyVAL.ruleNodeSlice, yyS[yypt-0].ruleNode) }
case 31:
//line parser.y:171
{ yyVAL.ruleNode = yyS[yypt-0].ruleNode }
case 32:
{ yyVAL.labelNameSlice = []model.LabelName{} }
case 29:
//line parser.y:169
{ yyVAL.labelNameSlice = yyS[yypt-1].labelNameSlice }
case 30:
//line parser.y:173
{ yyVAL.labelNameSlice = []model.LabelName{model.LabelName(yyS[yypt-0].str)} }
case 31:
//line parser.y:175
{ yyVAL.labelNameSlice = append(yyVAL.labelNameSlice, model.LabelName(yyS[yypt-0].str)) }
case 32:
//line parser.y:179
{ yyVAL.ruleNodeSlice = []ast.Node{yyS[yypt-0].ruleNode} }
case 33:
//line parser.y:181
{ yyVAL.ruleNodeSlice = append(yyVAL.ruleNodeSlice, yyS[yypt-0].ruleNode) }
case 34:
//line parser.y:185
{ yyVAL.ruleNode = yyS[yypt-0].ruleNode }
case 35:
//line parser.y:187
{ yyVAL.ruleNode = ast.NewStringLiteral(yyS[yypt-0].str) }
}
goto yystack /* stack new state and value */

View file

@ -20,21 +20,41 @@ import (
"time"
)
// A recorded rule.
type Rule struct {
// A Rule encapsulates a vector expression which is evaluated at a specified
// interval and acted upon (currently either recorded or used for alerting).
type Rule interface {
// Name returns the name of the rule.
Name() string
// EvalRaw evaluates the rule's vector expression without triggering any
// other actions, like recording or alerting.
EvalRaw(timestamp *time.Time) (vector ast.Vector, err error)
// Eval evaluates the rule, including any associated recording or alerting actions.
Eval(timestamp *time.Time) (vector ast.Vector, err error)
}
// A RecordingRule records its vector expression into new timeseries.
type RecordingRule struct {
name string
vector ast.VectorNode
labels model.LabelSet
permanent bool
}
func (rule *Rule) Name() string { return rule.name }
// An alerting rule generates alerts from its vector expression.
type AlertingRule struct {
name string
vector ast.VectorNode
holdDuration time.Duration
labels model.LabelSet
}
func (rule *Rule) EvalRaw(timestamp *time.Time) (vector ast.Vector, err error) {
func (rule RecordingRule) Name() string { return rule.name }
func (rule RecordingRule) EvalRaw(timestamp *time.Time) (vector ast.Vector, err error) {
return ast.EvalVectorInstant(rule.vector, *timestamp)
}
func (rule *Rule) Eval(timestamp *time.Time) (vector ast.Vector, err error) {
func (rule RecordingRule) Eval(timestamp *time.Time) (vector ast.Vector, err error) {
// Get the raw value of the rule expression.
vector, err = rule.EvalRaw(timestamp)
if err != nil {
@ -55,20 +75,46 @@ func (rule *Rule) Eval(timestamp *time.Time) (vector ast.Vector, err error) {
return
}
func (rule *Rule) RuleToDotGraph() string {
func (rule RecordingRule) RuleToDotGraph() string {
graph := "digraph \"Rules\" {\n"
graph += fmt.Sprintf("%#p[shape=\"box\",label=\"%v = \"];\n", rule, rule.name)
graph += fmt.Sprintf("%#p -> %#p;\n", rule, rule.vector)
graph += fmt.Sprintf("%#p -> %#p;\n", &rule, rule.vector)
graph += rule.vector.NodeTreeToDotGraph()
graph += "}\n"
return graph
}
func NewRule(name string, labels model.LabelSet, vector ast.VectorNode, permanent bool) *Rule {
return &Rule{
func (rule AlertingRule) Name() string { return rule.name }
func (rule AlertingRule) EvalRaw(timestamp *time.Time) (vector ast.Vector, err error) {
return ast.EvalVectorInstant(rule.vector, *timestamp)
}
func (rule AlertingRule) Eval(timestamp *time.Time) (vector ast.Vector, err error) {
// Get the raw value of the rule expression.
vector, err = rule.EvalRaw(timestamp)
if err != nil {
return
}
// TODO(julius): handle alerting.
return
}
func NewRecordingRule(name string, labels model.LabelSet, vector ast.VectorNode, permanent bool) *RecordingRule {
return &RecordingRule{
name: name,
labels: labels,
vector: vector,
permanent: permanent,
}
}
func NewAlertingRule(name string, vector ast.VectorNode, holdDuration time.Duration, labels model.LabelSet) *AlertingRule {
return &AlertingRule{
name: name,
vector: vector,
holdDuration: holdDuration,
labels: labels,
}
}

View file

@ -18,12 +18,16 @@ import (
"github.com/prometheus/prometheus/rules/ast"
"github.com/prometheus/prometheus/storage/metric"
"github.com/prometheus/prometheus/utility/test"
"path"
"strings"
"testing"
"time"
)
var testEvalTime = testStartTime.Add(testDuration5m * 10)
var (
testEvalTime = testStartTime.Add(testDuration5m * 10)
fixturesPath = "fixtures"
)
// Labels in expected output need to be alphabetically sorted.
var expressionTests = []struct {
@ -349,3 +353,70 @@ func TestExpressions(t *testing.T) {
}
}
}
var ruleTests = []struct {
inputFile string
shouldFail bool
errContains string
numRecordingRules int
numAlertingRules int
}{
{
inputFile: "empty.rules",
numRecordingRules: 0,
numAlertingRules: 0,
}, {
inputFile: "mixed.rules",
numRecordingRules: 2,
numAlertingRules: 2,
},
{
inputFile: "syntax_error.rules",
shouldFail: true,
errContains: "Error parsing rules at line 3",
},
{
inputFile: "non_vector.rules",
shouldFail: true,
errContains: "does not evaluate to vector type",
},
}
func TestRules(t *testing.T) {
for i, ruleTest := range ruleTests {
testRules, err := LoadRulesFromFile(path.Join(fixturesPath, ruleTest.inputFile))
if err != nil {
if !ruleTest.shouldFail {
t.Fatalf("%d. Error parsing rules file %v: %v", i, ruleTest.inputFile, err)
} else {
if !strings.Contains(err.Error(), ruleTest.errContains) {
t.Fatalf("%d. Expected error containing '%v', got: %v", i, ruleTest.errContains, err)
}
}
} else {
numRecordingRules := 0
numAlertingRules := 0
for j, rule := range testRules {
switch rule.(type) {
case *RecordingRule:
numRecordingRules++
case *AlertingRule:
numAlertingRules++
default:
t.Fatalf("%d.%d. Unknown rule type!", i, j)
}
}
if numRecordingRules != ruleTest.numRecordingRules {
t.Fatalf("%d. Expected %d recording rules, got %d", i, ruleTest.numRecordingRules, numRecordingRules)
}
if numAlertingRules != ruleTest.numAlertingRules {
t.Fatalf("%d. Expected %d alerting rules, got %d", i, ruleTest.numAlertingRules, numAlertingRules)
}
// TODO(julius): add more complex checks on the parsed rules here.
}
}
}