Skip to content

Commit

Permalink
Created strongly typed PolicyMatch struct from policy config to match…
Browse files Browse the repository at this point in the history
… values (grafana#3025)

* policy match attr struct

* optimizations

* optimizations

* separate policy matches for intrinsics & attributes

* CHANGELOG

* comment on benchmarks

* linter fixes

* building split policy intrinsic filters by attr.Intrinsic value

* revert pointer

* AttributeFilter do not match by status/kind, just string

* errors for default filter construction cases

* newSplitPolicy tests

* constructor NewStrictIntrinsicFilter

* safe casts

* removed unnecessary intrinsic filter ctor value type checks

* splitpolicy scoped attributes tests

* invalid regexp tests
  • Loading branch information
andriusluk authored Nov 9, 2023
1 parent 1299adc commit 4c0c132
Show file tree
Hide file tree
Showing 10 changed files with 1,412 additions and 973 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## main / unreleased

* [ENHANCEMENT] Introduced `AttributePolicyMatch` & `IntrinsicPolicyMatch` structures to match span attributes based on strongly typed values & precompiled regexp [#3025](https://github.com/grafana/tempo/pull/3025) (@andriusluk)
* [CHANGE] TraceQL/Structural operators performance improvement. [#3088](https://github.com/grafana/tempo/pull/3088) (@joe-elliott)
* [FEATURE] Introduce list_blocks_concurrency on GCS and S3 backends to control backend load and performance. [#2652](https://github.com/grafana/tempo/pull/2652) (@zalegrala)
* [BUGFIX] Include statusMessage intrinsic attribute in tag search. [#3084](https://github.com/grafana/tempo/pull/3084) (@rcrowe)
Expand Down
2 changes: 1 addition & 1 deletion pkg/spanfilter/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func ValidatePolicyMatch(match *PolicyMatch) error {
switch a.Intrinsic {
case traceql.IntrinsicKind, traceql.IntrinsicName, traceql.IntrinsicStatus: // currently supported
default:
return fmt.Errorf("currently unsupported intrinsic: %s; supported intrinsics: %q", a.Intrinsic, supportedIntrinsics)
return fmt.Errorf("unsupported intrinsic: %s; supported intrinsics: %q", a.Intrinsic, supportedIntrinsics)
}
}
}
Expand Down
151 changes: 151 additions & 0 deletions pkg/spanfilter/policymatch/attribute.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package policymatch

import (
"fmt"
"regexp"

"github.com/grafana/tempo/pkg/spanfilter/config"
commonv1 "github.com/grafana/tempo/pkg/tempopb/common/v1"
)

// AttributePolicyMatch is a set of attribute filters that must match a span for the span to match the policy.
type AttributePolicyMatch struct {
filters []AttributeFilter
}

// NewAttributePolicyMatch returns a new AttributePolicyMatch with the given filters. If no filters are given, then the policy matches all spans.
func NewAttributePolicyMatch(filters []AttributeFilter) *AttributePolicyMatch {
return &AttributePolicyMatch{filters: filters}
}

// Matches returns true if the given span matches the policy.
func (p *AttributePolicyMatch) Matches(attrs []*commonv1.KeyValue) bool {
// If there are no filters, then the span matches.
if len(p.filters) == 0 {
return true
}

// If there are no attributes, then the span does not match.
if len(attrs) == 0 {
return false
}

for _, pa := range p.filters {
if !matchesAnyFilter(pa, attrs) {
return false
}
}

return true
}

func matchesAnyFilter(pa AttributeFilter, attrs []*commonv1.KeyValue) bool {
for _, attr := range attrs {
// If the attribute key does not match, then it cannot match the policy.
if pa.key != attr.Key {
continue
}
switch pa.typ {
case StringAttributeFilter:
return pa.stringValue == attr.Value.GetStringValue()
case Int64AttributeFilter:
return pa.int64Value == attr.Value.GetIntValue()
case Float64AttributeFilter:
return pa.float64Value == attr.Value.GetDoubleValue()
case BoolAttributeFilter:
return pa.boolValue == attr.Value.GetBoolValue()
case RegexAttributeFilter:
return pa.regex.MatchString(attr.Value.GetStringValue())
}
}
return false
}

type AttributeFilterType int

const (
StringAttributeFilter AttributeFilterType = iota
Int64AttributeFilter
Float64AttributeFilter
BoolAttributeFilter
RegexAttributeFilter
)

// AttributeFilter is a filter that matches spans based on their attributes.
type AttributeFilter struct {
key string
typ AttributeFilterType
stringValue string
int64Value int64
float64Value float64
boolValue bool
regex *regexp.Regexp
}

// NewAttributeFilter returns a new AttributeFilter based on the match type.
func NewAttributeFilter(matchType config.MatchType, key string, value interface{}) (AttributeFilter, error) {
if matchType == config.Regex {
return NewRegexpAttributeFilter(key, value)
}
return NewStrictAttributeFilter(key, value)
}

// NewStrictAttributeFilter returns a new AttributeFilter that matches against the given value.
func NewStrictAttributeFilter(key string, value interface{}) (AttributeFilter, error) {
switch v := value.(type) {
case string:
return AttributeFilter{
key: key,
typ: StringAttributeFilter,
stringValue: v,
}, nil
case int:
return AttributeFilter{
key: key,
typ: Int64AttributeFilter,
int64Value: int64(v),
}, nil
case int64:
return AttributeFilter{
key: key,
typ: Int64AttributeFilter,
int64Value: v,
}, nil
case float64:
return AttributeFilter{
key: key,
typ: Float64AttributeFilter,
float64Value: v,
}, nil
case bool:
return AttributeFilter{
key: key,
typ: BoolAttributeFilter,
boolValue: v,
}, nil
default:
return AttributeFilter{}, fmt.Errorf("value type not supported: %T", value)
}
}

// NewRegexpAttributeFilter returns a new AttributeFilter that matches against the given regex value.
func NewRegexpAttributeFilter(key string, regex interface{}) (AttributeFilter, error) {
filter := AttributeFilter{
key: key,
typ: RegexAttributeFilter,
}
if stringValue, ok := regex.(string); ok {
compiled, err := regexp.Compile(stringValue)
if err != nil {
return filter, fmt.Errorf("invalid attribute filter regexp: %v", err)
}
filter.regex = compiled
}
if regexpValue, ok := regex.(*regexp.Regexp); ok {
filter.regex = regexpValue
}
if filter.regex == nil {
return filter, fmt.Errorf("invalid attribute filter regexp value: %v", regex)
}
return filter, nil
}
Loading

0 comments on commit 4c0c132

Please sign in to comment.