Skip to content

Commit

Permalink
[processor/tailsampling]: Enable inverse filtering for boolean attrib…
Browse files Browse the repository at this point in the history
…ute filter (open-telemetry#34730)

**Description:** This PR adds the `invert_match` option for
`boolean_attribute` filters, as discussed in
open-telemetry#34296 (comment)

**Link to tracking Issue:** open-telemetry#34296

**Testing:** Added unit tests

**Documentation:** The existing documentation already covered the
semantics of the `invert_match` option

---------

Signed-off-by: Florian Bacher <[email protected]>
  • Loading branch information
bacherfl authored Sep 18, 2024
1 parent 640428a commit a60ad92
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 10 deletions.
27 changes: 27 additions & 0 deletions .chloggen/tail_sampling_processor_inverted_bool_sampling.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Use this changelog template to create an entry for release notes.

# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: enhancement

# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
component: tailsamplingprocessor

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Fix the behavior for numeric tag filters with `inverse_match` set to `true`.

# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
issues: [34296]

# (Optional) One or more lines of additional information to render under the primary note.
# These lines will be padded with 2 spaces and then inserted directly into the document.
# Use pipe (|) for multiline entries.
subtext:

# If your change doesn't affect end users or the exported elements of any package,
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
# Optional: The change log or logs in which this entry should be included.
# e.g. '[user]' or '[user, api]'
# Include 'user' if the change is relevant to end users.
# Include 'api' if there is a change to a library API.
# Default: '[user]'
change_logs: []
11 changes: 10 additions & 1 deletion processor/tailsamplingprocessor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ Each policy will result in a decision, and the processor will evaluate them to m
- When there's a "inverted sample" decision and no "not sample" decisions, the trace is sampled;
- In all other cases, the trace is NOT sampled

An "inverted" decision is the one made based on the "invert_match" attribute, such as the one from the string tag policy.
An "inverted" decision is the one made based on the "invert_match" attribute, such as the one from the string, numeric or boolean tag policy.

Examples:

Expand Down Expand Up @@ -223,6 +223,8 @@ Imagine that you wish to configure the processor to implement the following rule

1. **Rule 6:** Add an escape hatch. If there is an attribute called `app.force_sample` in the span, then sample the trace at 100 percent.

1. **Rule 7:** Force spans with `app.do_not_sample` set to `true` to not be sampled, even if the result of the other rules yield a sampling decision.

Here is what the configuration would look like:

```yaml
Expand Down Expand Up @@ -407,6 +409,13 @@ tail_sampling:
type: boolean_attribute,
boolean_attribute: { key: app.force_sample, value: true },
},
{
# Rule 7:
# never sample if the do_not_sample attribute is set to true
name: team_a-do-not-sample,
type: boolean_attribute,
boolean_attribute: { key: app.do_not_sample, value: true, invert_match: true },
},
# END: policies for team_a
]
```
Expand Down
4 changes: 4 additions & 0 deletions processor/tailsamplingprocessor/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,10 @@ type BooleanAttributeCfg struct {
// Value indicate the bool value, either true or false to use when matching against attribute values.
// BooleanAttribute Policy will apply exact value match on Value
Value bool `mapstructure:"value"`
// InvertMatch indicates that values must not match against attribute values.
// If InvertMatch is true and Values is equal to 'true', all other values will be sampled except 'true'.
// Also, if the specified Key does not match any resource or span attributes, data will be sampled.
InvertMatch bool `mapstructure:"invert_match"`
}

// OTTLConditionCfg holds the configurable setting to create a OTTL condition filter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,22 @@ import (
)

type booleanAttributeFilter struct {
key string
value bool
logger *zap.Logger
key string
value bool
logger *zap.Logger
invertMatch bool
}

var _ PolicyEvaluator = (*booleanAttributeFilter)(nil)

// NewBooleanAttributeFilter creates a policy evaluator that samples all traces with
// the given attribute that match the supplied boolean value.
func NewBooleanAttributeFilter(settings component.TelemetrySettings, key string, value bool) PolicyEvaluator {
func NewBooleanAttributeFilter(settings component.TelemetrySettings, key string, value bool, invertMatch bool) PolicyEvaluator {
return &booleanAttributeFilter{
key: key,
value: value,
logger: settings.Logger,
key: key,
value: value,
logger: settings.Logger,
invertMatch: invertMatch,
}
}

Expand All @@ -36,6 +38,25 @@ func (baf *booleanAttributeFilter) Evaluate(_ context.Context, _ pcommon.TraceID
defer trace.Unlock()
batches := trace.ReceivedBatches

if baf.invertMatch {
return invertHasResourceOrSpanWithCondition(
batches,
func(resource pcommon.Resource) bool {
if v, ok := resource.Attributes().Get(baf.key); ok {
value := v.Bool()
return value != baf.value
}
return true
},
func(span ptrace.Span) bool {
if v, ok := span.Attributes().Get(baf.key); ok {
value := v.Bool()
return value != baf.value
}
return true
},
), nil
}
return hasResourceOrSpanWithCondition(
batches,
func(resource pcommon.Resource) bool {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
func TestBooleanTagFilter(t *testing.T) {

var empty = map[string]any{}
filter := NewBooleanAttributeFilter(componenttest.NewNopTelemetrySettings(), "example", true)
filter := NewBooleanAttributeFilter(componenttest.NewNopTelemetrySettings(), "example", true, false)

resAttr := map[string]any{}
resAttr["example"] = 8
Expand Down Expand Up @@ -54,6 +54,46 @@ func TestBooleanTagFilter(t *testing.T) {
}
}

func TestBooleanTagFilterInverted(t *testing.T) {

var empty = map[string]any{}
filter := NewBooleanAttributeFilter(componenttest.NewNopTelemetrySettings(), "example", true, true)

resAttr := map[string]any{}
resAttr["example"] = 8

cases := []struct {
Desc string
Trace *TraceData
Decision Decision
}{
{
Desc: "non-matching span attribute",
Trace: newTraceBoolAttrs(empty, "non_matching", true),
Decision: InvertSampled,
},
{
Desc: "span attribute with non matching boolean value",
Trace: newTraceBoolAttrs(empty, "example", false),
Decision: InvertSampled,
},
{
Desc: "span attribute with matching boolean value",
Trace: newTraceBoolAttrs(empty, "example", true),
Decision: InvertNotSampled,
},
}

for _, c := range cases {
t.Run(c.Desc, func(t *testing.T) {
u, _ := uuid.NewRandom()
decision, err := filter.Evaluate(context.Background(), pcommon.TraceID(u), c.Trace)
assert.NoError(t, err)
assert.Equal(t, decision, c.Decision)
})
}
}

func newTraceBoolAttrs(nodeAttrs map[string]any, spanAttrKey string, spanAttrValue bool) *TraceData {
traces := ptrace.NewTraces()
rs := traces.ResourceSpans().AppendEmpty()
Expand Down
2 changes: 1 addition & 1 deletion processor/tailsamplingprocessor/processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ func getSharedPolicyEvaluator(settings component.TelemetrySettings, cfg *sharedP
return sampling.NewTraceStateFilter(settings, tsfCfg.Key, tsfCfg.Values), nil
case BooleanAttribute:
bafCfg := cfg.BooleanAttributeCfg
return sampling.NewBooleanAttributeFilter(settings, bafCfg.Key, bafCfg.Value), nil
return sampling.NewBooleanAttributeFilter(settings, bafCfg.Key, bafCfg.Value, bafCfg.InvertMatch), nil
case OTTLCondition:
ottlfCfg := cfg.OTTLConditionCfg
return sampling.NewOTTLConditionFilter(settings, ottlfCfg.SpanConditions, ottlfCfg.SpanEventConditions, ottlfCfg.ErrorMode)
Expand Down

0 comments on commit a60ad92

Please sign in to comment.