From 20542895928de98b8a1c36e8b4a4855bd735a0b6 Mon Sep 17 00:00:00 2001 From: edmocosta <11836452+edmocosta@users.noreply.github.com> Date: Wed, 9 Oct 2024 18:01:38 +0200 Subject: [PATCH 1/3] Add OTTL context inferrer utility to resolve statements context based on its paths --- .../ottl-add-context-inferrer-utility.yaml | 29 +++++ pkg/ottl/context_inferrer.go | 85 ++++++++++++++ pkg/ottl/context_inferrer_test.go | 106 ++++++++++++++++++ 3 files changed, 220 insertions(+) create mode 100644 .chloggen/ottl-add-context-inferrer-utility.yaml create mode 100644 pkg/ottl/context_inferrer.go create mode 100644 pkg/ottl/context_inferrer_test.go diff --git a/.chloggen/ottl-add-context-inferrer-utility.yaml b/.chloggen/ottl-add-context-inferrer-utility.yaml new file mode 100644 index 000000000000..9b61c291497b --- /dev/null +++ b/.chloggen/ottl-add-context-inferrer-utility.yaml @@ -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] diff --git a/pkg/ottl/context_inferrer.go b/pkg/ottl/context_inferrer.go new file mode 100644 index 000000000000..a4963483d6f2 --- /dev/null +++ b/pkg/ottl/context_inferrer.go @@ -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 { + // Infer returns the OTTL context inferred from the given statements paths. + Infer(statements []string) (string, error) +} + +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 + } + } + } + + 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 { + contextPriority := make(map[string]int, len(contextsPriority)) + for i, ctx := range contextsPriority { + contextPriority[ctx] = i + } + return &priorityContextInferrer{ + contextPriority: contextPriority, + ignoreUnknownContext: ignoreUnknownContext, + } +} diff --git a/pkg/ottl/context_inferrer_test.go b/pkg/ottl/context_inferrer_test.go new file mode 100644 index 000000000000..6f4c5149b6a4 --- /dev/null +++ b/pkg/ottl/context_inferrer_test.go @@ -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]) + } +} From c0a8b672e62f0b444d9a137b2d48806e0f628e72 Mon Sep 17 00:00:00 2001 From: edmocosta <11836452+edmocosta@users.noreply.github.com> Date: Tue, 22 Oct 2024 15:20:54 +0200 Subject: [PATCH 2/3] Change to not expose the context inferrer and removed the ignoreUnknownContext option --- pkg/ottl/context_inferrer.go | 30 +++++++++------------ pkg/ottl/context_inferrer_test.go | 43 ++++++++++--------------------- 2 files changed, 25 insertions(+), 48 deletions(-) diff --git a/pkg/ottl/context_inferrer.go b/pkg/ottl/context_inferrer.go index a4963483d6f2..562e8387f550 100644 --- a/pkg/ottl/context_inferrer.go +++ b/pkg/ottl/context_inferrer.go @@ -18,18 +18,17 @@ var ( } ) -// ContextInferrer is an interface used to infer the OTTL context from statements paths. -type ContextInferrer interface { - // Infer returns the OTTL context inferred from the given statements paths. - Infer(statements []string) (string, error) +// contextInferrer is an interface used to infer the OTTL context from statements paths. +type contextInferrer interface { + // infer returns the OTTL context inferred from the given statements paths. + infer(statements []string) (string, error) } type priorityContextInferrer struct { - contextPriority map[string]int - ignoreUnknownContext bool + contextPriority map[string]int } -func (s *priorityContextInferrer) Infer(statements []string) (string, error) { +func (s *priorityContextInferrer) infer(statements []string) (string, error) { var inferredContext string var inferredContextPriority int @@ -42,10 +41,6 @@ func (s *priorityContextInferrer) Infer(statements []string) (string, error) { for _, p := range getParsedStatementPaths(parsed) { pathContextPriority, ok := s.contextPriority[p.Context] if !ok { - if s.ignoreUnknownContext { - continue - } - // Lowest priority pathContextPriority = math.MaxInt } @@ -60,26 +55,25 @@ func (s *priorityContextInferrer) Infer(statements []string) (string, error) { return inferredContext, nil } -// DefaultPriorityContextInferrer is like NewPriorityContextInferrer, but using the default +// defaultPriorityContextInferrer is like newPriorityContextInferrer, but using the default // context priorities and ignoring unknown/non-prioritized contexts. -func DefaultPriorityContextInferrer() ContextInferrer { - return NewPriorityContextInferrer(defaultContextInferPriority, false) +func defaultPriorityContextInferrer() contextInferrer { + return newPriorityContextInferrer(defaultContextInferPriority) } -// NewPriorityContextInferrer creates a new priority-based context inferrer. +// 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 { +func newPriorityContextInferrer(contextsPriority []string) contextInferrer { contextPriority := make(map[string]int, len(contextsPriority)) for i, ctx := range contextsPriority { contextPriority[ctx] = i } return &priorityContextInferrer{ - contextPriority: contextPriority, - ignoreUnknownContext: ignoreUnknownContext, + contextPriority: contextPriority, } } diff --git a/pkg/ottl/context_inferrer_test.go b/pkg/ottl/context_inferrer_test.go index 6f4c5149b6a4..4d4455dd0dcf 100644 --- a/pkg/ottl/context_inferrer_test.go +++ b/pkg/ottl/context_inferrer_test.go @@ -12,11 +12,10 @@ import ( func Test_NewPriorityContextInferrer_Infer(t *testing.T) { tests := []struct { - name string - priority []string - ignoreUnknown bool - statements []string - expected string + name string + priority []string + statements []string + expected string }{ { name: "with priority and contexts", @@ -45,32 +44,17 @@ func Test_NewPriorityContextInferrer_Infer(t *testing.T) { 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", + name: "with unknown context", + priority: []string{"foo", "bar"}, + statements: []string{"set(span.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) + inferrer := newPriorityContextInferrer(tt.priority) + inferredContext, err := inferrer.infer(tt.statements) require.NoError(t, err) assert.Equal(t, tt.expected, inferredContext) }) @@ -78,9 +62,9 @@ func Test_NewPriorityContextInferrer_Infer(t *testing.T) { } func Test_NewPriorityContextInferrer_InvalidStatement(t *testing.T) { - inferrer := NewPriorityContextInferrer([]string{"foo"}, false) + inferrer := newPriorityContextInferrer([]string{"foo"}) statements := []string{"set(foo.field,"} - _, err := inferrer.Infer(statements) + _, err := inferrer.infer(statements) require.ErrorContains(t, err, "unexpected token") } @@ -96,9 +80,8 @@ func Test_DefaultPriorityContextInferrer(t *testing.T) { "instrumentation_scope", } - inferrer := DefaultPriorityContextInferrer().(*priorityContextInferrer) + inferrer := defaultPriorityContextInferrer().(*priorityContextInferrer) require.NotNil(t, inferrer) - require.False(t, inferrer.ignoreUnknownContext) for pri, ctx := range expectedPriority { require.Equal(t, pri, inferrer.contextPriority[ctx]) From 3bce0c72e39b5c16a7fb301bea1eaa8fd05ee969 Mon Sep 17 00:00:00 2001 From: edmocosta <11836452+edmocosta@users.noreply.github.com> Date: Tue, 22 Oct 2024 17:23:36 +0200 Subject: [PATCH 3/3] Delete unneeded change log --- .../ottl-add-context-inferrer-utility.yaml | 29 ------------------- 1 file changed, 29 deletions(-) delete mode 100644 .chloggen/ottl-add-context-inferrer-utility.yaml diff --git a/.chloggen/ottl-add-context-inferrer-utility.yaml b/.chloggen/ottl-add-context-inferrer-utility.yaml deleted file mode 100644 index 9b61c291497b..000000000000 --- a/.chloggen/ottl-add-context-inferrer-utility.yaml +++ /dev/null @@ -1,29 +0,0 @@ -# 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]