Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[pkg/ottl] Add OTTL context inferrer utility #35721

Merged
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .chloggen/ottl-add-context-inferrer-utility.yaml
TylerHelmuth marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# 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: pkg/ottl

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Add OTTL context inferrer utility to resolve statements context based on its paths

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

# (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: |
The `ottl` package has a new interface `ottl.ContextInferrer`, and two new functions to create a priority-based
context inferrer `ottl.NewPriorityContextInferrer` and `ottl.DefaultPriorityContextInferrer`.

# 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: [api]
85 changes: 85 additions & 0 deletions pkg/ottl/context_inferrer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package ottl // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"

import "math"

var (
defaultContextInferPriority = []string{
"log",
"metric",
"datapoint",
"spanevent",
"span",
"resource",
"scope",
"instrumentation_scope",
}
)

// ContextInferrer is an interface used to infer the OTTL context from statements paths.
type ContextInferrer interface {
TylerHelmuth marked this conversation as resolved.
Show resolved Hide resolved
// Infer returns the OTTL context inferred from the given statements paths.
Infer(statements []string) (string, error)
TylerHelmuth marked this conversation as resolved.
Show resolved Hide resolved
}

type priorityContextInferrer struct {
contextPriority map[string]int
ignoreUnknownContext bool
}

func (s *priorityContextInferrer) Infer(statements []string) (string, error) {
var inferredContext string
var inferredContextPriority int

for _, statement := range statements {
parsed, err := parseStatement(statement)
if err != nil {
return inferredContext, err
}

for _, p := range getParsedStatementPaths(parsed) {
pathContextPriority, ok := s.contextPriority[p.Context]
if !ok {
if s.ignoreUnknownContext {
continue
}

// Lowest priority
pathContextPriority = math.MaxInt
}

if inferredContext == "" || pathContextPriority < inferredContextPriority {
inferredContext = p.Context
inferredContextPriority = pathContextPriority
}
}
}
TylerHelmuth marked this conversation as resolved.
Show resolved Hide resolved

return inferredContext, nil
}

// DefaultPriorityContextInferrer is like NewPriorityContextInferrer, but using the default
// context priorities and ignoring unknown/non-prioritized contexts.
func DefaultPriorityContextInferrer() ContextInferrer {
return NewPriorityContextInferrer(defaultContextInferPriority, false)
}

// NewPriorityContextInferrer creates a new priority-based context inferrer.
// To infer the context, it compares all [ottl.Path.Context] values, prioritizing them based
// on the provide contextsPriority argument, the lower the context position is in the array,
// the more priority it will have over other items.
// If unknown/non-prioritized contexts are found on the statements, they can be either ignored
// or considered when no other prioritized context is found. To skip unknown contexts, the
// ignoreUnknownContext argument must be set to false.
func NewPriorityContextInferrer(contextsPriority []string, ignoreUnknownContext bool) ContextInferrer {
TylerHelmuth marked this conversation as resolved.
Show resolved Hide resolved
contextPriority := make(map[string]int, len(contextsPriority))
for i, ctx := range contextsPriority {
contextPriority[ctx] = i
}
return &priorityContextInferrer{
contextPriority: contextPriority,
ignoreUnknownContext: ignoreUnknownContext,
TylerHelmuth marked this conversation as resolved.
Show resolved Hide resolved
}
}
106 changes: 106 additions & 0 deletions pkg/ottl/context_inferrer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package ottl

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func Test_NewPriorityContextInferrer_Infer(t *testing.T) {
tests := []struct {
name string
priority []string
ignoreUnknown bool
statements []string
expected string
}{
{
name: "with priority and contexts",
priority: []string{"spanevent", "span", "resource"},
statements: []string{"set(span.foo, resource.value) where spanevent.bar == true"},
expected: "spanevent",
},
{
name: "with multiple statements",
priority: []string{"spanevent", "span", "resource"},
statements: []string{
"set(resource.foo, resource.value) where span.bar == true",
"set(resource.foo, resource.value) where spanevent.bar == true",
},
expected: "spanevent",
},
{
name: "with no context",
priority: []string{"log", "resource"},
statements: []string{"set(foo, true) where bar == true"},
expected: "",
},
{
name: "with empty priority",
statements: []string{"set(foo.name, true) where bar.name == true"},
expected: "foo",
},
{
name: "with ignore unknown false",
priority: []string{"foo", "bar"},
ignoreUnknown: false,
statements: []string{"set(span.foo, true) where span.bar == true"},
expected: "span",
},
{
name: "with ignore unknown true",
priority: []string{"foo", "bar"},
ignoreUnknown: true,
statements: []string{"set(span.foo, true) where span.bar == true"},
expected: "",
},
{
name: "with ignore unknown true and mixed statement contexts",
priority: []string{"foo", "span"},
ignoreUnknown: true,
statements: []string{"set(bar.foo, true) where span.bar == true"},
expected: "span",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
inferrer := NewPriorityContextInferrer(tt.priority, tt.ignoreUnknown)
inferredContext, err := inferrer.Infer(tt.statements)
require.NoError(t, err)
assert.Equal(t, tt.expected, inferredContext)
})
}
}

func Test_NewPriorityContextInferrer_InvalidStatement(t *testing.T) {
inferrer := NewPriorityContextInferrer([]string{"foo"}, false)
statements := []string{"set(foo.field,"}
_, err := inferrer.Infer(statements)
require.ErrorContains(t, err, "unexpected token")
}

func Test_DefaultPriorityContextInferrer(t *testing.T) {
expectedPriority := []string{
"log",
"metric",
"datapoint",
"spanevent",
"span",
"resource",
"scope",
"instrumentation_scope",
}

inferrer := DefaultPriorityContextInferrer().(*priorityContextInferrer)
require.NotNil(t, inferrer)
require.False(t, inferrer.ignoreUnknownContext)

for pri, ctx := range expectedPriority {
require.Equal(t, pri, inferrer.contextPriority[ctx])
}
}
Loading