diff --git a/cfg/matchrule/do_if.go b/cfg/matchrule/do_if.go deleted file mode 100644 index 6ddedf04a..000000000 --- a/cfg/matchrule/do_if.go +++ /dev/null @@ -1,345 +0,0 @@ -package matchrule - -import ( - "bytes" - "errors" - "fmt" - "regexp" - "sync" - - "github.com/ozontech/file.d/cfg" - insaneJSON "github.com/vitkovskii/insane-json" -) - -type DoIfNodeType int - -const ( - DoIfNodeEmpty DoIfNodeType = iota - DoIfNodeFieldOp - DoIfNodeLogicalOp -) - -type DoIfNode interface { - Type() DoIfNodeType - Check(*insaneJSON.Root, map[string][]byte) bool - - getUniqueFields() map[string]struct{} -} - -type logicalOpType int - -const ( - logicalOpUnknown logicalOpType = iota - logicalOr - logicalAnd - logicalNot -) - -var ( - logicalOrBytes = []byte(`or`) - logicalAndBytes = []byte(`and`) - logicalNotBytes = []byte(`not`) -) - -type logicalNode struct { - op logicalOpType - operands []DoIfNode -} - -func NewLogicalNode(op []byte, operands []DoIfNode) (DoIfNode, error) { - if len(operands) == 0 { - return nil, errors.New("logical op must have at least one operand") - } - var lop logicalOpType - switch { - case bytes.Equal(op, logicalOrBytes): - lop = logicalOr - case bytes.Equal(op, logicalAndBytes): - lop = logicalAnd - case bytes.Equal(op, logicalNotBytes): - lop = logicalNot - if len(operands) > 1 { - return nil, fmt.Errorf("logical not must have exactly one operand, got %d", len(operands)) - } - default: - return nil, fmt.Errorf("unknown logical op %q", op) - } - return &logicalNode{ - op: lop, - operands: operands, - }, nil -} - -func (n *logicalNode) Type() DoIfNodeType { - return DoIfNodeLogicalOp -} - -func (n *logicalNode) Check(eventRoot *insaneJSON.Root, fieldsVals map[string][]byte) bool { - switch n.op { - case logicalOr: - for _, op := range n.operands { - if op.Check(eventRoot, fieldsVals) { - return true - } - } - return false - case logicalAnd: - for _, op := range n.operands { - if !op.Check(eventRoot, fieldsVals) { - return false - } - } - return true - case logicalNot: - return !n.operands[0].Check(eventRoot, fieldsVals) - } - return false -} - -func (n *logicalNode) getUniqueFields() map[string]struct{} { - result := make(map[string]struct{}) - for _, op := range n.operands { - for k := range op.getUniqueFields() { - result[k] = struct{}{} - } - } - return result -} - -type fieldOpType int - -const ( - unknownOp fieldOpType = iota - equalOp - containsOp - prefixOp - suffixOp - regexOp -) - -var ( - equalOpBytes = []byte(`equal`) - containsOpBytes = []byte(`contains`) - prefixOpBytes = []byte(`prefix`) - suffixOpBytes = []byte(`suffix`) - regexOpBytes = []byte(`regex`) -) - -type fieldOpNode struct { - op fieldOpType - fieldPath []string - fieldPathStr string - caseSensitive bool - values [][]byte - valuesBySize map[int][][]byte - reValues []*regexp.Regexp - - minValLen int - maxValLen int -} - -func NewFieldOpNode(op string, field string, caseSensitive bool, values [][]byte) (DoIfNode, error) { - if field == "" { - return nil, errors.New("field is not specified") - } - if len(values) == 0 { - return nil, errors.New("values are not provided") - } - var vals [][]byte - var valsBySize map[int][][]byte - var reValues []*regexp.Regexp - var minValLen, maxValLen int - var fop fieldOpType - - fieldPath := cfg.ParseFieldSelector(field) - - opBytes := []byte(op) - switch { - case bytes.Equal(opBytes, equalOpBytes): - fop = equalOp - case bytes.Equal(opBytes, containsOpBytes): - fop = containsOp - case bytes.Equal(opBytes, prefixOpBytes): - fop = prefixOp - case bytes.Equal(opBytes, suffixOpBytes): - fop = suffixOp - case bytes.Equal(opBytes, regexOpBytes): - fop = regexOp - reValues = make([]*regexp.Regexp, 0, len(values)) - for _, v := range values { - re, err := regexp.Compile(string(v)) - if err != nil { - return nil, fmt.Errorf("failed to compile regex %q: %w", v, err) - } - reValues = append(reValues, re) - } - default: - return nil, fmt.Errorf("unknown field op %q", op) - } - - if fop != regexOp { - minValLen = len(values[0]) - maxValLen = len(values[0]) - if fop == equalOp { - valsBySize = make(map[int][][]byte) - } else { - vals = make([][]byte, len(values)) - } - for i := range values { - var curVal []byte - if values[i] != nil { - curVal = make([]byte, len(values[i])) - copy(curVal, values[i]) - } - if !caseSensitive { - curVal = bytes.ToLower(curVal) - } - if len(values[i]) < minValLen { - minValLen = len(values[i]) - } - if len(values[i]) > maxValLen { - maxValLen = len(values[i]) - } - if fop == equalOp { - valsBySize[len(curVal)] = append(valsBySize[len(curVal)], curVal) - } else { - vals[i] = curVal - } - } - } - - return &fieldOpNode{ - op: fop, - fieldPath: fieldPath, - fieldPathStr: field, - caseSensitive: caseSensitive, - values: vals, - valuesBySize: valsBySize, - reValues: reValues, - minValLen: minValLen, - maxValLen: maxValLen, - }, nil -} - -func (n *fieldOpNode) Type() DoIfNodeType { - return DoIfNodeFieldOp -} - -func (n *fieldOpNode) Check(eventRoot *insaneJSON.Root, fieldsVals map[string][]byte) bool { - data, ok := fieldsVals[n.fieldPathStr] - if !ok { - node := eventRoot.Dig(n.fieldPath...) - data = node.AsBytes() - fieldsVals[n.fieldPathStr] = data - } - // fast check for data - if n.op != regexOp && len(data) < n.minValLen { - return false - } - switch n.op { - case equalOp: - vals, ok := n.valuesBySize[len(data)] - if !ok { - return false - } - if !n.caseSensitive { - data = bytes.ToLower(data) - } - for _, val := range vals { - if bytes.Equal(data, val) { - return true - } - } - case containsOp: - if !n.caseSensitive { - data = bytes.ToLower(data) - } - for _, val := range n.values { - if bytes.Contains(data, val) { - return true - } - } - case prefixOp: - // check only necessary amount of bytes - if len(data) > n.maxValLen { - data = data[:n.maxValLen] - } - if !n.caseSensitive { - data = bytes.ToLower(data) - } - for _, val := range n.values { - if bytes.HasPrefix(data, val) { - return true - } - } - case suffixOp: - // check only necessary amount of bytes - if len(data) > n.maxValLen { - data = data[len(data)-n.maxValLen:] - } - if !n.caseSensitive { - data = bytes.ToLower(data) - } - for _, val := range n.values { - if bytes.HasSuffix(data, val) { - return true - } - } - case regexOp: - for _, re := range n.reValues { - if re.Match(data) { - return true - } - } - } - return false -} - -func (n *fieldOpNode) getUniqueFields() map[string]struct{} { - return map[string]struct{}{ - n.fieldPathStr: {}, - } -} - -type DoIfChecker struct { - root DoIfNode - uniqueFieldsLen int - procsFieldsVals map[int]map[string][]byte - mu sync.RWMutex -} - -func NewDoIfChecker(root DoIfNode) *DoIfChecker { - uniqueFields := root.getUniqueFields() - return &DoIfChecker{ - root: root, - uniqueFieldsLen: len(uniqueFields), - procsFieldsVals: make(map[int]map[string][]byte), - } -} - -func (c *DoIfChecker) getProcFieldsVals(procID int) map[string][]byte { - c.mu.RLock() - data, ok := c.procsFieldsVals[procID] - if ok { - c.mu.RUnlock() - return data - } - c.mu.RUnlock() - c.mu.Lock() - defer c.mu.Unlock() - data = make(map[string][]byte, c.uniqueFieldsLen) - c.procsFieldsVals[procID] = data - return data -} - -func (c *DoIfChecker) Check(eventRoot *insaneJSON.Root, procID int) bool { - if eventRoot == nil { - return false - } - fieldsVals := c.getProcFieldsVals(procID) - result := c.root.Check(eventRoot, fieldsVals) - for k := range fieldsVals { - delete(fieldsVals, k) - } - return result -} diff --git a/fd/util.go b/fd/util.go index 7bc434545..4be53c8fe 100644 --- a/fd/util.go +++ b/fd/util.go @@ -185,15 +185,15 @@ func extractMetrics(actionJSON *simplejson.Json) (string, []string, bool) { return metricName, metricLabels, skipStatus } -func extractDoIfNode(jsonNode *simplejson.Json) (matchrule.DoIfNode, error) { - var result, operand matchrule.DoIfNode +func extractDoIfNode(jsonNode *simplejson.Json) (pipeline.DoIfNode, error) { + var result, operand pipeline.DoIfNode var err error logicalOpNode, has := jsonNode.CheckGet("logical_op") if has { // logical op node logicalOp := logicalOpNode.MustString() operands := jsonNode.Get("operands") - operandsList := make([]matchrule.DoIfNode, 0) + operandsList := make([]pipeline.DoIfNode, 0) for i := range operands.MustArray() { opNode := operands.GetIndex(i) operand, err = extractDoIfNode(opNode) @@ -202,7 +202,7 @@ func extractDoIfNode(jsonNode *simplejson.Json) (matchrule.DoIfNode, error) { } operandsList = append(operandsList, operand) } - result, err = matchrule.NewLogicalNode([]byte(logicalOp), operandsList) + result, err = pipeline.NewLogicalNode([]byte(logicalOp), operandsList) if err != nil { return nil, fmt.Errorf("failed to init logical node: %w", err) } @@ -230,7 +230,7 @@ func extractDoIfNode(jsonNode *simplejson.Json) (matchrule.DoIfNode, error) { vals = append(vals, []byte(curValue.(string))) } } - result, err = matchrule.NewFieldOpNode(fieldOp, fieldPath, caseSensitive, vals) + result, err = pipeline.NewFieldOpNode(fieldOp, fieldPath, caseSensitive, vals) if err != nil { return nil, fmt.Errorf("failed to init field op: %w", err) } @@ -238,7 +238,7 @@ func extractDoIfNode(jsonNode *simplejson.Json) (matchrule.DoIfNode, error) { return result, nil } -func extractDoIfChecker(actionJSON *simplejson.Json) (*matchrule.DoIfChecker, error) { +func extractDoIfChecker(actionJSON *simplejson.Json) (*pipeline.DoIfChecker, error) { if actionJSON.MustMap() == nil { return nil, nil } @@ -247,7 +247,7 @@ func extractDoIfChecker(actionJSON *simplejson.Json) (*matchrule.DoIfChecker, er if err != nil { return nil, fmt.Errorf("failed to extract nodes: %w", err) } - result := matchrule.NewDoIfChecker(root) + result := pipeline.NewDoIfChecker(root) return result, nil } diff --git a/pipeline/do_if.go b/pipeline/do_if.go new file mode 100644 index 000000000..3adec4c93 --- /dev/null +++ b/pipeline/do_if.go @@ -0,0 +1,604 @@ +package pipeline + +import ( + "bytes" + "errors" + "fmt" + "regexp" + "sync" + + "github.com/ozontech/file.d/cfg" + insaneJSON "github.com/vitkovskii/insane-json" +) + +// ! do-if-node +// ^ do-if-node + +type DoIfNodeType int + +const ( + DoIfNodeEmpty DoIfNodeType = iota + + // > Type of node where matching rules for fields are stored. + DoIfNodeFieldOp // * + + // > Type of node where logical rules for applying other rules are stored. + DoIfNodeLogicalOp // * +) + +type DoIfNode interface { + Type() DoIfNodeType + Check(*insaneJSON.Root, map[string][]byte) bool + + getUniqueFields() map[string]struct{} +} + +// ! do-if-field-op +// ^ do-if-field-op + +type doIfFieldOpType int + +const ( + doIfFieldUnknownOp doIfFieldOpType = iota + doIfFieldEqualOp + doIfFieldContainsOp + doIfFieldPrefixOp + doIfFieldSuffixOp + doIfFieldRegexOp +) + +var ( + // > checks whether the field value is equal to one of the elements in the values list. + // > + // > Example: + // > ```yaml + // > pipelines: + // > test: + // > actions: + // > - type: discard + // > do_if: + // > - field_op: equal + // > field: pod + // > values: [test-pod-1, test-pod-2] + // > ``` + // > + // > result: + // > ``` + // > {"pod":"test-pod-1","service":"test-service"} # discarded + // > {"pod":"test-pod-2","service":"test-service-2"} # discarded + // > {"pod":"test-pod","service":"test-service"} # not discarded + // > {"pod":"test-pod","service":"test-service-1"} # not discarded + // > ``` + doIfFieldEqualOpBytes = []byte(`equal`) // * + + // > checks whether the field value contains one of the elements the in values list. + // > + // > Example: + // > ```yaml + // > pipelines: + // > test: + // > actions: + // > - type: discard + // > do_if: + // > - field_op: contains + // > field: pod + // > values: [my-pod, my-test] + // > ``` + // > + // > result: + // > ``` + // > {"pod":"test-my-pod-1","service":"test-service"} # discarded + // > {"pod":"test-not-my-pod","service":"test-service-2"} # discarded + // > {"pod":"my-test-pod","service":"test-service"} # discarded + // > {"pod":"test-pod","service":"test-service-1"} # not discarded + // > ``` + doIfFieldContainsOpBytes = []byte(`contains`) // * + + // > checks whether the field value has prefix equal to one of the elements in the values list. + // > + // > Example: + // > ```yaml + // > pipelines: + // > test: + // > actions: + // > - type: discard + // > do_if: + // > - field_op: prefix + // > field: pod + // > values: [test-1, test-2] + // > ``` + // > + // > result: + // > ``` + // > {"pod":"test-1-pod-1","service":"test-service"} # discarded + // > {"pod":"test-2-pod-2","service":"test-service-2"} # discarded + // > {"pod":"test-pod","service":"test-service"} # not discarded + // > {"pod":"test-pod","service":"test-service-1"} # not discarded + // > ``` + doIfFieldPrefixOpBytes = []byte(`prefix`) // * + + // > checks whether the field value has suffix equal to one of the elements in the values list. + // > + // > Example: + // > ```yaml + // > pipelines: + // > test: + // > actions: + // > - type: discard + // > do_if: + // > - field_op: suffix + // > field: pod + // > values: [pod-1, pod-2] + // > ``` + // > + // > result: + // > ``` + // > {"pod":"test-1-pod-1","service":"test-service"} # discarded + // > {"pod":"test-2-pod-2","service":"test-service-2"} # discarded + // > {"pod":"test-pod","service":"test-service"} # not discarded + // > {"pod":"test-pod","service":"test-service-1"} # not discarded + // > ``` + doIfFieldSuffixOpBytes = []byte(`suffix`) // * + + // > checks whether the field matches any regex from the values list. + // > + // > Example: + // > ```yaml + // > pipelines: + // > test: + // > actions: + // > - type: discard + // > do_if: + // > - field_op: regex + // > field: pod + // > values: [pod-\d, my-test.*] + // > ``` + // > + // > result: + // > ``` + // > {"pod":"test-1-pod-1","service":"test-service"} # discarded + // > {"pod":"test-2-pod-2","service":"test-service-2"} # discarded + // > {"pod":"test-pod","service":"test-service"} # not discarded + // > {"pod":"my-test-pod","service":"test-service-1"} # discarded + // > {"pod":"my-test-instance","service":"test-service-1"} # discarded + // > {"pod":"service123","service":"test-service-1"} # not discarded + // > ``` + doIfFieldRegexOpBytes = []byte(`regex`) // * +) + +/*{ do-if-field-op-node +DoIf field op node is considered to always be a leaf in the DoIf tree. +It contains operation to be checked on the field value, the field name to extract data and +the values to check against. + +Params: + - `field_op` - value from field operations list. Required. + - `field` - name of the field to apply operation. Required. + - `values` - list of values to check field. Required non-empty. + - `case_sensitive` - flag indicating whether checks are performed in case sensitive way. Default `true`. + Note: case insensitive checks can cause CPU and memory overhead since every field value will be converted to lower letters. + +Example: +```yaml +pipelines: + tests: + actions: + - type: discard + do_if: + - field_op: suffix + field: pod + values: [pod-1, pod-2] + case_sensitive: true +``` + +}*/ + +type doIfFieldOpNode struct { + op doIfFieldOpType + fieldPath []string + fieldPathStr string + caseSensitive bool + values [][]byte + valuesBySize map[int][][]byte + reValues []*regexp.Regexp + + minValLen int + maxValLen int +} + +func NewFieldOpNode(op string, field string, caseSensitive bool, values [][]byte) (DoIfNode, error) { + if field == "" { + return nil, errors.New("field is not specified") + } + if len(values) == 0 { + return nil, errors.New("values are not provided") + } + var vals [][]byte + var valsBySize map[int][][]byte + var reValues []*regexp.Regexp + var minValLen, maxValLen int + var fop doIfFieldOpType + + fieldPath := cfg.ParseFieldSelector(field) + + opBytes := []byte(op) + switch { + case bytes.Equal(opBytes, doIfFieldEqualOpBytes): + fop = doIfFieldEqualOp + case bytes.Equal(opBytes, doIfFieldContainsOpBytes): + fop = doIfFieldContainsOp + case bytes.Equal(opBytes, doIfFieldPrefixOpBytes): + fop = doIfFieldPrefixOp + case bytes.Equal(opBytes, doIfFieldSuffixOpBytes): + fop = doIfFieldSuffixOp + case bytes.Equal(opBytes, doIfFieldRegexOpBytes): + fop = doIfFieldRegexOp + reValues = make([]*regexp.Regexp, 0, len(values)) + for _, v := range values { + re, err := regexp.Compile(string(v)) + if err != nil { + return nil, fmt.Errorf("failed to compile regex %q: %w", v, err) + } + reValues = append(reValues, re) + } + default: + return nil, fmt.Errorf("unknown field op %q", op) + } + + if fop != doIfFieldRegexOp { + minValLen = len(values[0]) + maxValLen = len(values[0]) + if fop == doIfFieldEqualOp { + valsBySize = make(map[int][][]byte) + } else { + vals = make([][]byte, len(values)) + } + for i := range values { + var curVal []byte + if values[i] != nil { + curVal = make([]byte, len(values[i])) + copy(curVal, values[i]) + } + if !caseSensitive { + curVal = bytes.ToLower(curVal) + } + if len(values[i]) < minValLen { + minValLen = len(values[i]) + } + if len(values[i]) > maxValLen { + maxValLen = len(values[i]) + } + if fop == doIfFieldEqualOp { + valsBySize[len(curVal)] = append(valsBySize[len(curVal)], curVal) + } else { + vals[i] = curVal + } + } + } + + return &doIfFieldOpNode{ + op: fop, + fieldPath: fieldPath, + fieldPathStr: field, + caseSensitive: caseSensitive, + values: vals, + valuesBySize: valsBySize, + reValues: reValues, + minValLen: minValLen, + maxValLen: maxValLen, + }, nil +} + +func (n *doIfFieldOpNode) Type() DoIfNodeType { + return DoIfNodeFieldOp +} + +func (n *doIfFieldOpNode) Check(eventRoot *insaneJSON.Root, fieldsVals map[string][]byte) bool { + data, ok := fieldsVals[n.fieldPathStr] + if !ok { + node := eventRoot.Dig(n.fieldPath...) + data = node.AsBytes() + fieldsVals[n.fieldPathStr] = data + } + // fast check for data + if n.op != doIfFieldRegexOp && len(data) < n.minValLen { + return false + } + switch n.op { + case doIfFieldEqualOp: + vals, ok := n.valuesBySize[len(data)] + if !ok { + return false + } + if !n.caseSensitive { + data = bytes.ToLower(data) + } + for _, val := range vals { + if bytes.Equal(data, val) { + return true + } + } + case doIfFieldContainsOp: + if !n.caseSensitive { + data = bytes.ToLower(data) + } + for _, val := range n.values { + if bytes.Contains(data, val) { + return true + } + } + case doIfFieldPrefixOp: + // check only necessary amount of bytes + if len(data) > n.maxValLen { + data = data[:n.maxValLen] + } + if !n.caseSensitive { + data = bytes.ToLower(data) + } + for _, val := range n.values { + if bytes.HasPrefix(data, val) { + return true + } + } + case doIfFieldSuffixOp: + // check only necessary amount of bytes + if len(data) > n.maxValLen { + data = data[len(data)-n.maxValLen:] + } + if !n.caseSensitive { + data = bytes.ToLower(data) + } + for _, val := range n.values { + if bytes.HasSuffix(data, val) { + return true + } + } + case doIfFieldRegexOp: + for _, re := range n.reValues { + if re.Match(data) { + return true + } + } + } + return false +} + +func (n *doIfFieldOpNode) getUniqueFields() map[string]struct{} { + return map[string]struct{}{ + n.fieldPathStr: {}, + } +} + +// ! do-if-logical-op +// ^ do-if-logical-op + +type doIfLogicalOpType int + +const ( + doIfLogicalOpUnknown doIfLogicalOpType = iota + doIfLogicalOr + doIfLogicalAnd + doIfLogicalNot +) + +var ( + // > accepts at least one operand and returns true on the first returned true from its operands. + // > + // > Example: + // > ```yaml + // > pipelines: + // > test: + // > actions: + // > - type: discard + // > do_if: + // > - logical_op: or + // > operands: + // > - field_op: equal + // > field: pod + // > values: [test-pod-1, test-pod-2] + // > - field_op: equal + // > field: service + // > values: [test-service] + // > ``` + // > + // > result: + // > ``` + // > {"pod":"test-pod-1","service":"test-service"} # discarded + // > {"pod":"test-pod-2","service":"test-service-2"} # discarded + // > {"pod":"test-pod","service":"test-service"} # discarded + // > {"pod":"test-pod","service":"test-service-1"} # not discarded + // > ``` + doIfLogicalOrBytes = []byte(`or`) // * + + // > accepts at least one operand and returns true if all operands return true + // > (in other words returns false on the first returned false from its operands). + // > + // > Example: + // > ```yaml + // > pipelines: + // > test: + // > actions: + // > - type: discard + // > do_if: + // > - logical_op: and + // > operands: + // > - field_op: equal + // > field: pod + // > values: [test-pod-1, test-pod-2] + // > - field_op: equal + // > field: service + // > values: [test-service] + // > ``` + // > + // > result: + // > ``` + // > {"pod":"test-pod-1","service":"test-service"} # discarded + // > {"pod":"test-pod-2","service":"test-service-2"} # not discarded + // > {"pod":"test-pod","service":"test-service"} # not discarded + // > {"pod":"test-pod","service":"test-service-1"} # not discarded + // > ``` + doIfLogicalAndBytes = []byte(`and`) // * + + // > accepts exactly one operand and returns inverted result of its operand. + // > + // > Example: + // > ```yaml + // > pipelines: + // > test: + // > actions: + // > - type: discard + // > do_if: + // > - logical_op: not + // > operands: + // > - field_op: equal + // > field: service + // > values: [test-service] + // > ``` + // > + // > result: + // > ``` + // > {"pod":"test-pod-1","service":"test-service"} # not discarded + // > {"pod":"test-pod-2","service":"test-service-2"} # discarded + // > {"pod":"test-pod","service":"test-service"} # not discarded + // > {"pod":"test-pod","service":"test-service-1"} # discarded + // > ``` + doIfLogicalNotBytes = []byte(`not`) // * +) + +/*{ do-if-logical-op-node +DoIf logical op node is a node considered to be the root or an edge between nodes. +It always has at least one operand which are other nodes and calls their checks +to apply logical operation on their results. + +Params: + - `logical_op` - value from logical operations list. Required. + - `operands` - list of another do-if nodes. Required non-empty. + +Example: +```yaml +pipelines: + test: + actions: + - type: discard + do_if: + - logical_op: and + operands: + - field_op: equal + field: pod + values: [test-pod-1, test-pod-2] + case_sensitive: true + - field_op: equal + field: service + values: [test-service] + case_sensitive: true +``` + +}*/ + +type doIfLogicalNode struct { + op doIfLogicalOpType + operands []DoIfNode +} + +func NewLogicalNode(op []byte, operands []DoIfNode) (DoIfNode, error) { + if len(operands) == 0 { + return nil, errors.New("logical op must have at least one operand") + } + var lop doIfLogicalOpType + switch { + case bytes.Equal(op, doIfLogicalOrBytes): + lop = doIfLogicalOr + case bytes.Equal(op, doIfLogicalAndBytes): + lop = doIfLogicalAnd + case bytes.Equal(op, doIfLogicalNotBytes): + lop = doIfLogicalNot + if len(operands) > 1 { + return nil, fmt.Errorf("logical not must have exactly one operand, got %d", len(operands)) + } + default: + return nil, fmt.Errorf("unknown logical op %q", op) + } + return &doIfLogicalNode{ + op: lop, + operands: operands, + }, nil +} + +func (n *doIfLogicalNode) Type() DoIfNodeType { + return DoIfNodeLogicalOp +} + +func (n *doIfLogicalNode) Check(eventRoot *insaneJSON.Root, fieldsVals map[string][]byte) bool { + switch n.op { + case doIfLogicalOr: + for _, op := range n.operands { + if op.Check(eventRoot, fieldsVals) { + return true + } + } + return false + case doIfLogicalAnd: + for _, op := range n.operands { + if !op.Check(eventRoot, fieldsVals) { + return false + } + } + return true + case doIfLogicalNot: + return !n.operands[0].Check(eventRoot, fieldsVals) + } + return false +} + +func (n *doIfLogicalNode) getUniqueFields() map[string]struct{} { + result := make(map[string]struct{}) + for _, op := range n.operands { + for k := range op.getUniqueFields() { + result[k] = struct{}{} + } + } + return result +} + +type DoIfChecker struct { + root DoIfNode + uniqueFieldsLen int + procsFieldsVals map[int]map[string][]byte + mu sync.RWMutex +} + +func NewDoIfChecker(root DoIfNode) *DoIfChecker { + uniqueFields := root.getUniqueFields() + return &DoIfChecker{ + root: root, + uniqueFieldsLen: len(uniqueFields), + procsFieldsVals: make(map[int]map[string][]byte), + } +} + +func (c *DoIfChecker) getProcFieldsVals(procID int) map[string][]byte { + c.mu.RLock() + data, ok := c.procsFieldsVals[procID] + if ok { + c.mu.RUnlock() + return data + } + c.mu.RUnlock() + c.mu.Lock() + defer c.mu.Unlock() + data = make(map[string][]byte, c.uniqueFieldsLen) + c.procsFieldsVals[procID] = data + return data +} + +func (c *DoIfChecker) Check(eventRoot *insaneJSON.Root, procID int) bool { + if eventRoot == nil { + return false + } + fieldsVals := c.getProcFieldsVals(procID) + result := c.root.Check(eventRoot, fieldsVals) + for k := range fieldsVals { + delete(fieldsVals, k) + } + return result +} diff --git a/pipeline/plugin.go b/pipeline/plugin.go index fe33c75bb..40d84b1cb 100644 --- a/pipeline/plugin.go +++ b/pipeline/plugin.go @@ -5,7 +5,6 @@ import ( "regexp" "strings" - "github.com/ozontech/file.d/cfg/matchrule" "github.com/ozontech/file.d/metric" "go.uber.org/zap" ) @@ -98,7 +97,7 @@ type ActionPluginStaticInfo struct { MatchMode MatchMode MatchInvert bool - DoIfChecker *matchrule.DoIfChecker + DoIfChecker *DoIfChecker } type ActionPluginInfo struct {