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

[processor/transform] Add business logic for handling traces queries. #7300

Merged
merged 19 commits into from
Feb 16, 2022
Merged
Show file tree
Hide file tree
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
26 changes: 10 additions & 16 deletions processor/transformprocessor/internal/common/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ import (
"github.com/alecthomas/participle/v2/lexer"
)

// Query represents a parsed query. It is the entry point into the query DSL.
// ParsedQuery represents a parsed query. It is the entry point into the query DSL.
// nolint:govet
type Query struct {
type ParsedQuery struct {
Invocation Invocation `@@`
Condition *Condition `( "where" @@ )?`
}
Expand Down Expand Up @@ -65,27 +65,21 @@ type Field struct {
MapKey *string `( "[" @String "]" )?`
}

func Parse(rawQueries []string) ([]Query, error) {
func Parse(raw string) (*ParsedQuery, error) {
parser, err := newParser()
bogdandrutu marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return []Query{}, err
return &ParsedQuery{}, err
}

parsed := make([]Query, 0)

for _, raw := range rawQueries {
query := Query{}
err = parser.ParseString("", raw, &query)
if err != nil {
return []Query{}, err
}
parsed = append(parsed, query)
parsed := &ParsedQuery{}
err = parser.ParseString("", raw, parsed)
if err != nil {
return nil, err
}

return parsed, nil
}

// newParser returns a parser that can be used to read a string into a Query. An error will be returned if the string
// newParser returns a parser that can be used to read a string into a ParsedQuery. An error will be returned if the string
// is not formatted for the DSL.
func newParser() (*participle.Parser, error) {
lex := lexer.MustSimple([]lexer.Rule{
Expand All @@ -96,7 +90,7 @@ func newParser() (*participle.Parser, error) {
{Name: `Operators`, Pattern: `==|!=|[,.()\[\]]`, Action: nil},
{Name: "whitespace", Pattern: `\s+`, Action: nil},
})
return participle.Build(&Query{},
return participle.Build(&ParsedQuery{},
participle.Lexer(lex),
participle.Unquote("String"),
participle.Elide("whitespace"),
Expand Down
27 changes: 13 additions & 14 deletions processor/transformprocessor/internal/common/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ import (
func Test_parse(t *testing.T) {
tests := []struct {
query string
expected Query
expected *ParsedQuery
}{
{
query: `set("foo")`,
expected: Query{
expected: &ParsedQuery{
Invocation: Invocation{
Function: "set",
Arguments: []Value{
Expand All @@ -41,7 +41,7 @@ func Test_parse(t *testing.T) {
},
{
query: `met(1.2)`,
expected: Query{
expected: &ParsedQuery{
Invocation: Invocation{
Function: "met",
Arguments: []Value{
Expand All @@ -55,7 +55,7 @@ func Test_parse(t *testing.T) {
},
{
query: `fff(12)`,
expected: Query{
expected: &ParsedQuery{
Invocation: Invocation{
Function: "fff",
Arguments: []Value{
Expand All @@ -69,7 +69,7 @@ func Test_parse(t *testing.T) {
},
{
query: `set("foo", get(bear.honey))`,
expected: Query{
expected: &ParsedQuery{
Invocation: Invocation{
Function: "set",
Arguments: []Value{
Expand Down Expand Up @@ -102,7 +102,7 @@ func Test_parse(t *testing.T) {
},
{
query: `set(foo.attributes["bar"].cat, "dog")`,
expected: Query{
expected: &ParsedQuery{
Invocation: Invocation{
Function: "set",
Arguments: []Value{
Expand Down Expand Up @@ -132,7 +132,7 @@ func Test_parse(t *testing.T) {
},
{
query: `set(foo.attributes["bar"].cat, "dog") where name == "fido"`,
expected: Query{
expected: &ParsedQuery{
Invocation: Invocation{
Function: "set",
Arguments: []Value{
Expand Down Expand Up @@ -176,7 +176,7 @@ func Test_parse(t *testing.T) {
},
{
query: `set(foo.attributes["bar"].cat, "dog") where name != "fido"`,
expected: Query{
expected: &ParsedQuery{
Invocation: Invocation{
Function: "set",
Arguments: []Value{
Expand Down Expand Up @@ -220,7 +220,7 @@ func Test_parse(t *testing.T) {
},
{
query: `set ( foo.attributes[ "bar"].cat, "dog") where name=="fido"`,
expected: Query{
expected: &ParsedQuery{
Invocation: Invocation{
Function: "set",
Arguments: []Value{
Expand Down Expand Up @@ -264,7 +264,7 @@ func Test_parse(t *testing.T) {
},
{
query: `set("fo\"o")`,
expected: Query{
expected: &ParsedQuery{
Invocation: Invocation{
Function: "set",
Arguments: []Value{
Expand All @@ -280,10 +280,9 @@ func Test_parse(t *testing.T) {

for _, tt := range tests {
t.Run(tt.query, func(t *testing.T) {
parsed, err := Parse([]string{tt.query})
parsed, err := Parse(tt.query)
assert.NoError(t, err)
assert.Len(t, parsed, 1)
assert.Equal(t, tt.expected, parsed[0])
assert.Equal(t, tt.expected, parsed)
})
}
}
Expand All @@ -298,7 +297,7 @@ func Test_parse_failure(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt, func(t *testing.T) {
_, err := Parse([]string{tt})
_, err := Parse(tt)
assert.Error(t, err)
})
}
Expand Down
54 changes: 54 additions & 0 deletions processor/transformprocessor/internal/traces/condition.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package traces // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/transformprocessor/internal/traces"

import (
"fmt"

"go.opentelemetry.io/collector/model/pdata"

"github.com/open-telemetry/opentelemetry-collector-contrib/processor/transformprocessor/internal/common"
)

func newConditionEvaluator(cond *common.Condition) (func(span pdata.Span, il pdata.InstrumentationLibrary, resource pdata.Resource) bool, error) {
bogdandrutu marked this conversation as resolved.
Show resolved Hide resolved
if cond == nil {
return func(span pdata.Span, il pdata.InstrumentationLibrary, resource pdata.Resource) bool {
return true
}, nil
anuraaga marked this conversation as resolved.
Show resolved Hide resolved
}
left, err := newGetter(cond.Left)
if err != nil {
return nil, err
}
right, err := newGetter(cond.Right)
if err != nil {
return nil, err
}
anuraaga marked this conversation as resolved.
Show resolved Hide resolved

switch cond.Op {
case "==":
return func(span pdata.Span, il pdata.InstrumentationLibrary, resource pdata.Resource) bool {
a := left.get(span, il, resource)
b := right.get(span, il, resource)
return a == b
}, nil
anuraaga marked this conversation as resolved.
Show resolved Hide resolved
case "!=":
return func(span pdata.Span, il pdata.InstrumentationLibrary, resource pdata.Resource) bool {
return left.get(span, il, resource) != right.get(span, il, resource)
}, nil
}

return nil, fmt.Errorf("unrecognized boolean operation %v", cond.Op)
}
136 changes: 136 additions & 0 deletions processor/transformprocessor/internal/traces/condition_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package traces

import (
"testing"

"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/model/pdata"

"github.com/open-telemetry/opentelemetry-collector-contrib/processor/transformprocessor/internal/common"
)

func Test_newConditionEvaluator(t *testing.T) {
span := pdata.NewSpan()
span.SetName("bear")
tests := []struct {
name string
cond *common.Condition
matching pdata.Span
}{
{
name: "literals match",
cond: &common.Condition{
Left: common.Value{
String: strp("hello"),
},
Right: common.Value{
String: strp("hello"),
},
Op: "==",
},
matching: span,
},
{
name: "literals don't match",
cond: &common.Condition{
Left: common.Value{
String: strp("hello"),
},
Right: common.Value{
String: strp("goodbye"),
},
Op: "!=",
},
matching: span,
},
{
name: "path expression matches",
cond: &common.Condition{
Left: common.Value{
Path: &common.Path{
Fields: []common.Field{
{
Name: "name",
},
},
},
},
Right: common.Value{
String: strp("bear"),
},
Op: "==",
},
matching: span,
},
{
name: "path expression not matches",
cond: &common.Condition{
Left: common.Value{
Path: &common.Path{
Fields: []common.Field{
{
Name: "name",
},
},
},
},
Right: common.Value{
String: strp("bear"),
},
Op: "==",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be !=) I don't see any difference between this and the test case above.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doh thanks

},
matching: span,
},
{
name: "no condition",
cond: nil,
matching: span,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
evaluate, err := newConditionEvaluator(tt.cond)
assert.NoError(t, err)
assert.True(t, evaluate(tt.matching, pdata.NewInstrumentationLibrary(), pdata.NewResource()))
})
}

t.Run("invalid", func(t *testing.T) {
_, err := newConditionEvaluator(&common.Condition{
Left: common.Value{
String: strp("bear"),
},
Op: "<>",
Right: common.Value{
String: strp("cat"),
},
})
assert.Error(t, err)
})
}

func strp(s string) *string {
return &s
}

func intp(i int64) *int64 {
return &i
}

func floatp(f float64) *float64 {
return &f
}
Loading