Skip to content

Commit eaa2e69

Browse files
committed
Address child depth issues with scripts. Add tree address validation to test.
1 parent 3352280 commit eaa2e69

File tree

7 files changed

+121
-16
lines changed

7 files changed

+121
-16
lines changed

pkg/ast/ast.go

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ func (b *builderT) buildLeafChild(parserNode *parser.NodeT, machineAddress *AstN
197197
case parserNode.IsMatcherNode():
198198
leaf, err = b.buildMatcherChild(parserNode, machineAddress, termIdx)
199199
case parserNode.IsPromNode():
200-
leaf, err = b.buildPromQLNode(parserNode, machineAddress, termIdx)
200+
leaf, err = b.buildPromQLChild(parserNode, machineAddress, termIdx)
201201
}
202202
return
203203
}
@@ -248,9 +248,11 @@ func (b *builderT) buildMatcherChild(parserNode *parser.NodeT, machineAddress *A
248248
return nil, parserNode.WrapError(ErrInvalidEventType)
249249
}
250250

251-
// Implied that the root node has an origin event
252-
b.OriginCnt++
253-
parserNode.Metadata.Event.Origin = true
251+
// This appears to be a legacy hack to support rules that don't specify origin but have event sources.
252+
// We should consider removing this and requiring explicit origin specification in the rules.
253+
if b.CurrentDepth == 0 && !parserNode.Metadata.Event.Origin {
254+
parserNode.Metadata.Event.Origin = true
255+
}
254256

255257
err = b.descendTree(func() error {
256258
if matchNode, err = b.buildMatcherNodes(parserNode, machineAddress, termIdx); err != nil {
@@ -329,10 +331,6 @@ func (b *builderT) buildMachineChildren(parserNode *parser.NodeT, machineAddress
329331

330332
// If the child has an event/data source, then it is not a state machine. Build it via buildMatcherNodes
331333

332-
if parserChildNode.Metadata.Event.Origin {
333-
b.OriginCnt++
334-
}
335-
336334
if parserChildNode.Metadata.Event.Source == "" {
337335
log.Error().
338336
Any("address", machineAddress).

pkg/ast/ast_log.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,10 @@ func (b *builderT) doBuildLogMatcherNode(parserNode *parser.NodeT, machineAddres
152152
Correlations: parserNode.Metadata.Correlations,
153153
}
154154

155+
if parserNode.Metadata.Event.Origin {
156+
b.OriginCnt++
157+
}
158+
155159
return matchNode, nil
156160
}
157161

pkg/ast/ast_metrics.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,22 @@ type AstPromQL struct {
1515
Event *AstEventT
1616
}
1717

18+
func (b *builderT) buildPromQLChild(parserNode *parser.NodeT, machineAddress *AstNodeAddressT, termIdx *uint32) (*AstNodeT, error) {
19+
var child *AstNodeT
20+
21+
err := b.descendTree(func() error {
22+
node, err := b.buildPromQLNode(parserNode, machineAddress, termIdx)
23+
if err != nil {
24+
return err
25+
}
26+
child = node
27+
return nil
28+
})
29+
30+
return child, err
31+
32+
}
33+
1834
func (b *builderT) buildPromQLNode(parserNode *parser.NodeT, machineAddress *AstNodeAddressT, termIdx *uint32) (*AstNodeT, error) {
1935

2036
// Expects one child of type ParsePromQL
@@ -45,6 +61,9 @@ func (b *builderT) buildPromQLNode(parserNode *parser.NodeT, machineAddress *Ast
4561
Source: parserNode.Metadata.Event.Source,
4662
Origin: parserNode.Metadata.Event.Origin,
4763
}
64+
if parserNode.Metadata.Event.Origin {
65+
b.OriginCnt++
66+
}
4867
}
4968

5069
if promNode.Interval != nil {

pkg/ast/ast_script.go

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,19 +39,25 @@ func (b *builderT) buildScriptChildren(parserNode *parser.NodeT, machineAddress
3939

4040
leaf, err := b.buildLeafChild(parserChildNode, machineAddress, &termIdx)
4141

42+
var childList []*AstNodeT
43+
4244
switch {
4345
case err != nil:
44-
return nil, err
46+
// fallthrough
4547
case leaf != nil:
46-
return []*AstNodeT{leaf}, nil
48+
childList = []*AstNodeT{leaf}
4749
default:
48-
node, err := b.buildTree(parserChildNode, machineAddress, &termIdx)
49-
if err != nil {
50-
return nil, err
51-
}
52-
53-
return []*AstNodeT{node}, nil
50+
err = b.descendTree(func() error {
51+
node, err := b.buildTree(parserChildNode, machineAddress, &termIdx)
52+
if err != nil {
53+
return err
54+
}
55+
childList = []*AstNodeT{node}
56+
return nil
57+
})
5458
}
59+
60+
return childList, err
5561
}
5662

5763
// Validate script definitions and build the script node.

pkg/ast/ast_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ func TestAstSuccess(t *testing.T) {
8484
rule: testdata.TestSuccessChildScriptMultipleInputs,
8585
expectedNodeTypes: []string{"machine_set", "script", "machine_seq", "log_seq", "log_set"},
8686
},
87+
"Success_ChildScriptPromQLInput": {
88+
rule: testdata.TestSuccessChildScriptPromQLInput,
89+
expectedNodeTypes: []string{"machine_set", "script", "promql"},
90+
},
8791
}
8892

8993
for name, test := range tests {
@@ -104,6 +108,10 @@ func TestAstSuccess(t *testing.T) {
104108
t.Fatalf("No nodes found in AST")
105109
}
106110

111+
if err = validateTree(ast.Nodes[0]); err != nil {
112+
t.Fatalf("Error validating tree: %v", err)
113+
}
114+
107115
var actualNodes []string
108116
gatherNodeTypes(ast.Nodes[0], &actualNodes)
109117

@@ -273,3 +281,45 @@ func TestFailureExamples(t *testing.T) {
273281
}
274282
}
275283
}
284+
285+
// Validate the following invariants on the tree:
286+
// 1. No duplicate addresses
287+
// 2. Root node has no parent address
288+
// 3. Node ids are unique
289+
// 4. Depth is consistent with distance from root
290+
291+
func validateTree(node *AstNodeT) error {
292+
if node == nil {
293+
return fmt.Errorf("Root node is nil")
294+
}
295+
296+
if node.Metadata.ParentAddress != nil {
297+
return fmt.Errorf("Root node has parent address: %s", node.Metadata.ParentAddress.String())
298+
}
299+
300+
return _validateTree(node, 0, make(map[uint32]struct{})) // start at depth 0 for root
301+
}
302+
303+
func _validateTree(node *AstNodeT, depth uint32, ids map[uint32]struct{}) error {
304+
305+
if node == nil {
306+
return nil
307+
}
308+
309+
if node.Metadata.Address.Depth != depth {
310+
return fmt.Errorf("Node %s has depth %d, expected %d", node.Metadata.Address.String(), node.Metadata.Address.Depth, depth)
311+
}
312+
313+
if _, exists := ids[node.Metadata.Address.NodeId]; exists {
314+
return fmt.Errorf("Duplicate node ID %d found", node.Metadata.Address.NodeId)
315+
}
316+
ids[node.Metadata.Address.NodeId] = struct{}{}
317+
318+
for _, child := range node.Children {
319+
if err := _validateTree(child, depth+1, ids); err != nil {
320+
return err
321+
}
322+
}
323+
324+
return nil
325+
}

pkg/parser/parse_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,11 @@ func TestParseSuccess(t *testing.T) {
8585
expectedNodeTypes: []string{"machine_set", "script", "machine_seq", "log_seq", "log_set"},
8686
expectedNegIndexes: []int{-1, -1, -1, -1, -1},
8787
},
88+
"Success_ChildScriptPromQLInput": {
89+
rule: testdata.TestSuccessChildScriptPromQLInput,
90+
expectedNodeTypes: []string{"machine_set", "script", "promql"},
91+
expectedNegIndexes: []int{-1, -1, -1},
92+
},
8893
}
8994

9095
for name, test := range tests {

pkg/testdata/rules.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,29 @@ rules:
460460
- value: term4
461461
`
462462

463+
var TestSuccessChildScriptPromQLInput = `
464+
rules:
465+
- cre:
466+
id: TestSuccessChildScriptPromQLInput
467+
metadata:
468+
id: "J7uRQTGpGMyL1iFpssnBeS"
469+
hash: "rdJLgqYgkEp8jg8Qks1qiq"
470+
generation: 1
471+
rule:
472+
set:
473+
window: 30s
474+
match:
475+
- script:
476+
code: "function process(ev) print(\"Processing...\") end"
477+
input:
478+
promql:
479+
event:
480+
source: cre.metrics
481+
origin: true
482+
expr: 'sum(rate(http_requests_total[5m])) by (service)'
483+
interval: 10s
484+
`
485+
463486
/* Failure cases */
464487
var TestFailTypo = ` # Line 1 starts here
465488
rules:

0 commit comments

Comments
 (0)