diff --git a/go.mod b/go.mod index 6385841..5c4ed88 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,6 @@ require ( github.com/btcsuite/btcutil v1.0.2 github.com/prequel-dev/prequel-logmatch v0.0.13 github.com/rs/zerolog v1.34.0 - gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 03bcca4..9228adb 100644 --- a/go.sum +++ b/go.sum @@ -63,7 +63,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/ast/ast.go b/pkg/ast/ast.go index f39a47e..239140b 100644 --- a/pkg/ast/ast.go +++ b/pkg/ast/ast.go @@ -161,11 +161,17 @@ func (b *builderT) buildTree(parserNode *parser.NodeT, parentMachineAddress *Ast ) // Build children (either matcher children or nested machines) - if isMatcherNode(parserNode) { + if parserNode.IsMatcherNode() { if matchNode, err = b.buildMatcherChildren(parserNode, machineAddress, termIdx); err != nil { return nil, err } children = append(children, matchNode) + } else if parserNode.IsPromNode() { + if matchNode, err = b.buildPromQLNode(parserNode, machineAddress, termIdx); err != nil { + return nil, err + } + children = append(children, matchNode) + } else { if children, err = b.buildMachineChildren(parserNode, machineAddress); err != nil { return nil, err @@ -210,20 +216,6 @@ func newAstNode(parserNode *parser.NodeT, typ schema.NodeTypeT, scope string, pa } } -func isMatcherNode(node *parser.NodeT) bool { - var ( - hasMatcher = true - ) - - for _, child := range node.Children { - if _, ok := child.(*parser.MatcherT); !ok { - hasMatcher = false - } - } - - return hasMatcher -} - func (b *builderT) buildMatcherChildren(parserNode *parser.NodeT, machineAddress *AstNodeAddressT, termIdx *uint32) (*AstNodeT, error) { var ( @@ -265,6 +257,8 @@ func (b *builderT) buildMatcherNodes(parserNode *parser.NodeT, machineAddress *A switch parserNode.Metadata.Type { case schema.NodeTypeLogSeq: case schema.NodeTypeLogSet: + case schema.NodeTypePromQL: + return b.buildPromQLNode(parserNode, machineAddress, termIdx) default: return nil, parserNode.WrapError(ErrInvalidNodeType) } @@ -372,7 +366,7 @@ func (b *builderT) buildStateMachine(parserNode *parser.NodeT, parentMachineAddr Msg("Window is required for sequences") return nil, parserNode.WrapError(ErrInvalidWindow) } - case schema.NodeTypeSet, schema.NodeTypeLogSet: + case schema.NodeTypeSet, schema.NodeTypeLogSet, schema.NodeTypePromQL: default: log.Error(). Any("address", machineAddress). diff --git a/pkg/ast/ast_log.go b/pkg/ast/ast_log.go index 53e5125..ca8daba 100644 --- a/pkg/ast/ast_log.go +++ b/pkg/ast/ast_log.go @@ -202,6 +202,7 @@ func newMatchTerm(field parser.FieldT) (AstFieldT, error) { } return t, nil + } func newNegateTerm(field parser.FieldT, anchors uint32) (AstFieldT, error) { diff --git a/pkg/ast/ast_machine.go b/pkg/ast/ast_machine.go index a71c3af..e3a7a72 100644 --- a/pkg/ast/ast_machine.go +++ b/pkg/ast/ast_machine.go @@ -24,25 +24,31 @@ type AstSetMatcherT struct { func (b *builderT) buildMachineNode(parserNode *parser.NodeT, parentMachineAddress, machineAddress *AstNodeAddressT, children []*AstNodeT) (*AstNodeT, error) { var ( - seqMatcher *AstSeqMatcherT - setMatcher *AstSetMatcherT - matchNode = newAstNode(parserNode, parserNode.Metadata.Type, schema.ScopeCluster, parentMachineAddress, machineAddress) - err error + matchNode = newAstNode(parserNode, parserNode.Metadata.Type, schema.ScopeCluster, parentMachineAddress, machineAddress) ) switch parserNode.Metadata.Type { case schema.NodeTypeSeq, schema.NodeTypeLogSeq: matchNode.Metadata.Type = schema.NodeTypeSeq - if seqMatcher, err = buildSeqMatcher(parserNode, children); err != nil { + if seqMatcher, err := buildSeqMatcher(parserNode, children); err != nil { return nil, err + } else { + matchNode.Object = seqMatcher } - matchNode.Object = seqMatcher case schema.NodeTypeSet, schema.NodeTypeLogSet: matchNode.Metadata.Type = schema.NodeTypeSet - if setMatcher, err = buildSetMatcher(parserNode, children); err != nil { + if setMatcher, err := buildSetMatcher(parserNode, children); err != nil { return nil, err + } else { + matchNode.Object = setMatcher + } + case schema.NodeTypePromQL: + matchNode.Metadata.Type = schema.NodeTypePromQL + if promMatcher, err := b.buildPromQLNode(parserNode, machineAddress, nil); err != nil { + return nil, err + } else { + matchNode.Object = promMatcher } - matchNode.Object = setMatcher default: log.Error(). Str("type", parserNode.Metadata.Type.String()). diff --git a/pkg/ast/ast_metrics.go b/pkg/ast/ast_metrics.go new file mode 100644 index 0000000..5249a0d --- /dev/null +++ b/pkg/ast/ast_metrics.go @@ -0,0 +1,69 @@ +package ast + +import ( + "time" + + "github.com/prequel-dev/prequel-compiler/pkg/parser" + "github.com/prequel-dev/prequel-compiler/pkg/schema" + "github.com/rs/zerolog/log" +) + +type AstPromQL struct { + Expr string + For time.Duration + Interval time.Duration + Event *AstEventT +} + +func (b *builderT) buildPromQLNode(parserNode *parser.NodeT, machineAddress *AstNodeAddressT, termIdx *uint32) (*AstNodeT, error) { + + // Expects one child of type ParsePromQL + + if len(parserNode.Children) != 1 { + log.Error().Int("child_count", len(parserNode.Children)).Msg("PromQL node must have exactly one child") + return nil, parserNode.WrapError(ErrInvalidNodeType) + } + + promNode, ok := parserNode.Children[0].(*parser.PromQLT) + + if !ok { + log.Error().Any("promql", parserNode.Children[0]).Msg("Failed to build PromQL node") + return nil, parserNode.WrapError(ErrMissingScalar) + } + + if promNode.Expr == "" { + log.Error().Msg("PromQL Expr string is empty") + return nil, parserNode.WrapError(ErrMissingScalar) + } + + pn := &AstPromQL{ + Expr: promNode.Expr, + } + + if parserNode.Metadata.Event != nil { + if parserNode.Metadata.Event.Origin { + b.HasOrigin = true + } + pn.Event = &AstEventT{ + Source: parserNode.Metadata.Event.Source, + Origin: parserNode.Metadata.Event.Origin, + } + } + + if promNode.Interval != nil { + pn.Interval = *promNode.Interval + } + + if promNode.For != nil { + pn.For = *promNode.For + } + + var ( + address = b.newAstNodeAddress(parserNode.Metadata.RuleHash, parserNode.Metadata.Type.String(), termIdx) + node = newAstNode(parserNode, parserNode.Metadata.Type, schema.ScopeCluster, machineAddress, address) + ) + + node.Object = pn + return node, nil + +} diff --git a/pkg/ast/ast_test.go b/pkg/ast/ast_test.go index d87fd7d..5d49271 100644 --- a/pkg/ast/ast_test.go +++ b/pkg/ast/ast_test.go @@ -69,6 +69,10 @@ func TestAstSuccess(t *testing.T) { rule: testdata.TestSuccessSimpleExtraction, expectedNodeTypes: []string{"machine_seq", "log_seq"}, }, + "Success_PromQLMetric": { + rule: testdata.TestSuccessSimplePromQL, + expectedNodeTypes: []string{"machine_set", "promql", "log_set"}, + }, } for name, test := range tests { @@ -172,7 +176,7 @@ func TestAstFail(t *testing.T) { "Fail_TermsSemanticError4": { rule: testdata.TestFailTermsSemanticError4, err: ErrInvalidEventType, - line: 16, + line: 14, col: 11, }, "Fail_TermsSemanticError5": { diff --git a/pkg/datasrc/parse.go b/pkg/datasrc/parse.go index e5528fb..901fa92 100644 --- a/pkg/datasrc/parse.go +++ b/pkg/datasrc/parse.go @@ -4,7 +4,7 @@ import ( "os" "time" - "gopkg.in/yaml.v2" + "gopkg.in/yaml.v3" ) // version: 0.0.1 diff --git a/pkg/parser/parse.go b/pkg/parser/parse.go index 30c72bc..b41abfc 100644 --- a/pkg/parser/parse.go +++ b/pkg/parser/parse.go @@ -101,6 +101,7 @@ type ParseTermT struct { Set *ParseSetT `yaml:"set,omitempty"` Sequence *ParseSequenceT `yaml:"sequence,omitempty"` NegateOpts *ParseNegateOptsT `yaml:",inline,omitempty"` + PromQL *ParsePromQL `yaml:"promql,omitempty"` Extract []ParseExtractT `yaml:"extract,omitempty"` } @@ -118,6 +119,13 @@ type ParseExtractT struct { RegexValue string `yaml:"regex,omitempty"` } +type ParsePromQL struct { + Expr string `yaml:"expr"` + Interval string `yaml:"interval,omitempty"` + For string `yaml:"for,omitempty"` + Event *ParseEventT `yaml:"event,omitempty"` +} + func (o *ParseTermT) UnmarshalYAML(unmarshal func(any) error) error { var str string if err := unmarshal(&str); err == nil { @@ -125,15 +133,16 @@ func (o *ParseTermT) UnmarshalYAML(unmarshal func(any) error) error { return nil } var temp struct { - Field string `yaml:"field,omitempty"` - StrValue string `yaml:"value,omitempty"` - JqValue string `yaml:"jq,omitempty"` - RegexValue string `yaml:"regex,omitempty"` - Count int `yaml:"count,omitempty"` - Set *ParseSetT `yaml:"set,omitempty"` - Sequence *ParseSequenceT `yaml:"sequence,omitempty"` - NegateOpts *ParseNegateOptsT `yaml:",inline,omitempty"` - Extract []ParseExtractT `yaml:"extract,omitempty"` + Field string `yaml:"field,omitempty"` + StrValue string `yaml:"value,omitempty"` + JqValue string `yaml:"jq,omitempty"` + RegexValue string `yaml:"regex,omitempty"` + Count int `yaml:"count,omitempty"` + Set *ParseSetT `yaml:"set,omitempty"` + Sequence *ParseSequenceT `yaml:"sequence,omitempty"` + NegateOpts *ParseNegateOptsT `yaml:",inline,omitempty"` + ParsePromQL *ParsePromQL `yaml:"promql,omitempty"` + Extract []ParseExtractT `yaml:"extract,omitempty"` } if err := unmarshal(&temp); err != nil { return err @@ -146,6 +155,7 @@ func (o *ParseTermT) UnmarshalYAML(unmarshal func(any) error) error { o.Set = temp.Set o.Sequence = temp.Sequence o.NegateOpts = temp.NegateOpts + o.PromQL = temp.ParsePromQL o.Extract = temp.Extract return nil } diff --git a/pkg/parser/parse_test.go b/pkg/parser/parse_test.go index 3b4b398..cc542e7 100644 --- a/pkg/parser/parse_test.go +++ b/pkg/parser/parse_test.go @@ -61,15 +61,20 @@ func TestParseSuccess(t *testing.T) { expectedNegIndexes: []int{-1, 2, 2, -1, -1, -1, -1}, }, "Success_MissingRuleId": { - rule: testdata.TestFailMissingRuleIdRule, + rule: testdata.TestFailMissingRuleIdRule, expectedNodeTypes: []string{"log_set"}, expectedNegIndexes: []int{-1}, - }, - "Success_MissingRuleHash": { - rule: testdata.TestFailMissingRuleHashRule, + }, + "Success_MissingRuleHash": { + rule: testdata.TestFailMissingRuleHashRule, expectedNodeTypes: []string{"log_set"}, expectedNegIndexes: []int{-1}, - }, + }, + "Success_PromQL": { + rule: testdata.TestSuccessSimplePromQL, + expectedNodeTypes: []string{"machine_set", "promql", "log_set"}, + expectedNegIndexes: []int{-1, -1, -1}, + }, } for name, test := range tests { diff --git a/pkg/parser/tree.go b/pkg/parser/tree.go index 54f15ae..699c795 100644 --- a/pkg/parser/tree.go +++ b/pkg/parser/tree.go @@ -11,6 +11,7 @@ import ( "time" "github.com/btcsuite/btcutil/base58" + "github.com/prequel-dev/prequel-compiler/pkg/pqerr" "github.com/prequel-dev/prequel-compiler/pkg/schema" "github.com/rs/zerolog/log" @@ -34,6 +35,7 @@ var ( ErrInvalidRuleId = errors.New("invalid rule id (must be base58)") ErrInvalidRuleHash = errors.New("invalid rule hash (must be base58)") ErrExtractName = errors.New("invalid extract name (alphanumeric and underscores only)") + ErrInnerEvent = errors.New("invalid event on inner node") ) var ( @@ -102,6 +104,16 @@ type MatcherT struct { Window time.Duration `json:"window"` } +type PromQLT struct { + Expr string `json:"expr"` + For *time.Duration `json:"for,omitempty"` + Interval *time.Duration `json:"interval,omitempty"` +} + +// PromQLValidator validates a PromQL expression. +// Hook exposed to avoid importing promql dependencies in compiler. +var PromQLValidator = func(expr string) error { return nil } + func newEvent(t *ParseEventT) *EventT { return &EventT{ Source: t.Source, @@ -159,17 +171,91 @@ func initNode(ruleId, ruleHash string, creId string, yn *yaml.Node) (*NodeT, err }, nil } -func seqNodeProps(node *NodeT, seq *ParseSequenceT, order bool, yn *yaml.Node) error { +func assignNodeSeq(node *NodeT, seq *ParseSequenceT) error { + + if seq.Event == nil { + node.Metadata.Type = schema.NodeTypeSeq + return nil + } + + // Propagate the event + node.Metadata.Event = newEvent(seq.Event) + + switch { + case node.IsPromNode(): + node.Metadata.Type = schema.NodeTypePromQL + case !node.IsMatcherNode(): + return ErrInnerEvent + default: + node.Metadata.Type = schema.NodeTypeLogSeq + } + + return nil +} + +func assignNodeSet(node *NodeT, set *ParseSetT) error { + + if set.Event == nil { + node.Metadata.Type = schema.NodeTypeSet + return nil + } + + // Propagate the event + node.Metadata.Event = newEvent(set.Event) + + switch { + case node.IsPromNode(): + node.Metadata.Type = schema.NodeTypePromQL + case !node.IsMatcherNode(): + return ErrInnerEvent + default: + node.Metadata.Type = schema.NodeTypeLogSet + } + + return nil +} + +func (node *NodeT) IsMatcherNode() bool { + if len(node.Children) == 0 { + return false + } + + allMatcher := true + + for _, child := range node.Children { + if _, ok := child.(*MatcherT); !ok { + allMatcher = false + break + } + } + + return allMatcher +} + +func (node *NodeT) IsPromNode() bool { + if len(node.Children) == 0 { + return false + } + + allPromQL := true + for _, child := range node.Children { + if _, ok := child.(*PromQLT); !ok { + allPromQL = false + break + } + } + + return allPromQL +} - node.Metadata.Type = schema.NodeTypeSeq +func seqNodeProps(node *NodeT, seq *ParseSequenceT, order bool, yn *yaml.Node) error { if !order { return node.WrapError(ErrMissingOrder) } - if seq.Event != nil { - node.Metadata.Type = schema.NodeTypeLogSeq - node.Metadata.Event = newEvent(seq.Event) + if err := assignNodeSeq(node, seq); err != nil { + return err } if seq.Window != "" { @@ -193,15 +279,12 @@ func seqNodeProps(node *NodeT, seq *ParseSequenceT, order bool, yn *yaml.Node) e func setNodeProps(node *NodeT, set *ParseSetT, match bool, yn *yaml.Node) error { - node.Metadata.Type = schema.NodeTypeSet - if !match { return node.WrapError(ErrMissingMatch) } - if set.Event != nil { - node.Metadata.Type = schema.NodeTypeLogSet - node.Metadata.Event = newEvent(set.Event) + if err := assignNodeSet(node, set); err != nil { + return err } if set.Window != "" { @@ -312,11 +395,6 @@ func buildSequenceTree(root *NodeT, termsT map[string]ParseTermT, r ParseRuleT, return nil, err } - // Apply sequence-specific node properties - if err := seqNodeProps(root, seq, seq.Order != nil, orderYn); err != nil { - return nil, err - } - // Order positive first, then negatives root.Children = append(root.Children, pos...) root.Children = append(root.Children, neg...) @@ -324,6 +402,11 @@ func buildSequenceTree(root *NodeT, termsT map[string]ParseTermT, r ParseRuleT, root.NegIdx = len(pos) } + // Apply sequence-specific node properties + if err := seqNodeProps(root, seq, seq.Order != nil, orderYn); err != nil { + return nil, err + } + return root, nil } @@ -356,11 +439,6 @@ func buildSetTree(root *NodeT, termsT map[string]ParseTermT, r ParseRuleT, ruleN return nil, err } - // Apply set-specific node properties - if err := setNodeProps(root, set, set.Match != nil, ruleNode); err != nil { - return nil, err - } - // Order positive first, then negatives root.Children = append(root.Children, pos...) root.Children = append(root.Children, neg...) @@ -368,6 +446,11 @@ func buildSetTree(root *NodeT, termsT map[string]ParseTermT, r ParseRuleT, ruleN root.NegIdx = len(pos) } + // Apply set-specific node properties + if err := setNodeProps(root, set, set.Match != nil, ruleNode); err != nil { + return nil, err + } + return root, nil } @@ -376,9 +459,6 @@ func buildSetTree(root *NodeT, termsT map[string]ParseTermT, r ParseRuleT, ruleN // is being treated as negated or not. func buildChildrenGroups(root *NodeT, termsT map[string]ParseTermT, matches, negates []ParseTermT, orderYn, negateYn *yaml.Node, termsY map[string]*yaml.Node) (pos []any, neg []any, err error) { - pos = []any{} - neg = []any{} - if len(matches) > 0 { cPos, err := buildChildren(root, termsT, matches, false, orderYn, termsY) @@ -440,49 +520,68 @@ func buildChildren(parent *NodeT, tm map[string]ParseTermT, terms []ParseTermT, return children, nil } -func nodeFromTerm(parent *NodeT, termsT map[string]ParseTermT, term ParseTermT, parentNegate bool, yn *yaml.Node, termsY map[string]*yaml.Node) (any, error) { +func nodeFromSeq(parent *NodeT, termsT map[string]ParseTermT, term ParseTermT, yn *yaml.Node, termsY map[string]*yaml.Node) (node *NodeT, err error) { - var ( - node *NodeT - opts *NegateOptsT - n *yaml.Node - err error - ok bool - ) + n, ok := findChild(yn, docSeq) + if !ok { + n = yn + } - switch { - case term.Sequence != nil: + node, err = buildSequenceNode(parent, termsT, term.Sequence, n, termsY) + if err != nil { + return + } - if n, ok = findChild(yn, docSeq); !ok { - n = yn - } + if term.NegateOpts == nil { + return + } - if node, err = buildSequenceNode(parent, termsT, term.Sequence, n, termsY); err != nil { - return nil, err - } + opts, err := negateOpts(term) + if err != nil { + return + } + node.Metadata.NegateOpts = opts - if term.NegateOpts != nil { - if opts, err = negateOpts(term); err != nil { - return nil, err - } - node.Metadata.NegateOpts = opts - } - case term.Set != nil: + return +} - if n, ok = findChild(yn, docSet); !ok { - n = yn - } +func nodeFromSet(parent *NodeT, termsT map[string]ParseTermT, term ParseTermT, yn *yaml.Node, termsY map[string]*yaml.Node) (node *NodeT, err error) { - if node, err = buildSetNode(parent, termsT, term.Set, n, termsY); err != nil { - return nil, err - } + n, ok := findChild(yn, docSet) + if !ok { + n = yn + } + + node, err = buildSetNode(parent, termsT, term.Set, n, termsY) + if err != nil { + return + } + + if term.NegateOpts == nil { + return + } + + opts, err := negateOpts(term) + if err != nil { + return + } + node.Metadata.NegateOpts = opts + + return +} + +func nodeFromTerm(parent *NodeT, termsT map[string]ParseTermT, term ParseTermT, parentNegate bool, yn *yaml.Node, termsY map[string]*yaml.Node) (v any, err error) { + + switch { + case term.Sequence != nil: + v, err = nodeFromSeq(parent, termsT, term, yn, termsY) + + case term.Set != nil: + v, err = nodeFromSet(parent, termsT, term, yn, termsY) + + case term.PromQL != nil: + return nodeFromProm(parent, term, yn) - if term.NegateOpts != nil { - if opts, err = negateOpts(term); err != nil { - return nil, err - } - node.Metadata.NegateOpts = opts - } case term.StrValue != "" || term.JqValue != "" || term.RegexValue != "": return parseValue(term, parentNegate) @@ -491,7 +590,7 @@ func nodeFromTerm(parent *NodeT, termsT map[string]ParseTermT, term ParseTermT, return nil, parent.WrapError(ErrTermNotFound) } - return node, nil + return } func extractTerms(terms []ParseExtractT) ([]ExtractT, error) { @@ -546,16 +645,17 @@ func buildSequenceNode(parent *NodeT, termsT map[string]ParseTermT, seq *ParseSe return nil, err } - // Apply sequence-specific node properties - if err := seqNodeProps(node, seq, seq.Order != nil, yn); err != nil { - return nil, err - } - node.Children = append(node.Children, pos...) node.Children = append(node.Children, neg...) if len(neg) > 0 { node.NegIdx = len(pos) } + + // Apply sequence-specific node properties + if err := seqNodeProps(node, seq, seq.Order != nil, yn); err != nil { + return nil, err + } + return node, nil } @@ -570,16 +670,17 @@ func buildSetNode(parent *NodeT, termsT map[string]ParseTermT, set *ParseSetT, y return nil, err } - // Apply set-specific node properties - if err := setNodeProps(node, set, set.Match != nil, yn); err != nil { - return nil, err - } - node.Children = append(node.Children, pos...) node.Children = append(node.Children, neg...) if len(neg) > 0 { node.NegIdx = len(pos) } + + // Apply set-specific node properties + if err := setNodeProps(node, set, set.Match != nil, yn); err != nil { + return nil, err + } + return node, nil } @@ -608,6 +709,51 @@ func buildPosNegChildren(node *NodeT, termsT map[string]ParseTermT, matches, neg return pos, neg, nil } +func nodeFromProm(parent *NodeT, term ParseTermT, yn *yaml.Node) (*NodeT, error) { + + var interval *time.Duration + if term.PromQL.Interval != "" { + dur, err := time.ParseDuration(term.PromQL.Interval) + if err != nil { + return nil, err + } + interval = &dur + } + + var forDuration *time.Duration + if term.PromQL.For != "" { + dur, err := time.ParseDuration(term.PromQL.For) + if err != nil { + return nil, err + } + forDuration = &dur + } + + if err := PromQLValidator(term.PromQL.Expr); err != nil { + return nil, err + } + + node, err := initNode(parent.Metadata.RuleId, parent.Metadata.RuleHash, parent.Metadata.CreId, yn) + if err != nil { + return nil, parent.WrapError(err) + } + + node.Metadata.Type = schema.NodeTypePromQL + + // Propagate the event + if term.PromQL.Event != nil { + node.Metadata.Event = newEvent(term.PromQL.Event) + } + + node.Children = append(node.Children, &PromQLT{ + Expr: term.PromQL.Expr, + For: forDuration, + Interval: interval, + }) + + return node, nil +} + func parseValue(term ParseTermT, negate bool) (*MatcherT, error) { var ( diff --git a/pkg/schema/schema.go b/pkg/schema/schema.go index 291bf67..3c9275e 100644 --- a/pkg/schema/schema.go +++ b/pkg/schema/schema.go @@ -14,7 +14,7 @@ const ( NodeTypeSet NodeTypeT = "machine_set" NodeTypeLogSeq NodeTypeT = "log_seq" NodeTypeLogSet NodeTypeT = "log_set" - NodeTypeDesc NodeTypeT = "desc" + NodeTypePromQL NodeTypeT = "promql" ) func (t NodeTypeT) String() string { diff --git a/pkg/testdata/rules.go b/pkg/testdata/rules.go index 27831b1..9bdf804 100644 --- a/pkg/testdata/rules.go +++ b/pkg/testdata/rules.go @@ -906,8 +906,6 @@ rules: generation: 1 rule: set: - event: - source: kafka correlations: - hostname match: @@ -929,8 +927,6 @@ rules: generation: 1 rule: set: - event: - source: kafka correlations: - hostname match: @@ -1085,3 +1081,28 @@ rules: match: - regex: "io.vertx.core.VertxException: Thread blocked" ` + +var TestSuccessSimplePromQL = ` +rules: + - cre: + id: TestSuccessSimplePromQL + metadata: + id: "J7uRQTGpGMyL1iFpssnBeS" + hash: "rdJLgqYgkEp8jg8Qks1qiq" + generation: 1 + rule: + set: + window: 50s + match: + - promql: + event: + source: cre.metrics + origin: true + expr: 'sum(rate(http_requests_total[5m])) by (service)' + interval: 10s + - set: + event: + source: kafka + match: + - regex: "io.vertx.core.VertxException: Thread blocked" +`