Skip to content

Commit

Permalink
[processor/transform] Add business logic for handling traces queries.
Browse files Browse the repository at this point in the history
  • Loading branch information
anuraaga committed Jan 21, 2022
1 parent d22de4b commit 33715c7
Show file tree
Hide file tree
Showing 13 changed files with 2,061 additions and 30 deletions.
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()
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) {
if cond == nil {
return func(span pdata.Span, il pdata.InstrumentationLibrary, resource pdata.Resource) bool {
return true
}, nil
}
left, err := newGetter(cond.Left)
if err != nil {
return nil, err
}
right, err := newGetter(cond.Right)
if err != nil {
return nil, err
}

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
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: "==",
},
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

0 comments on commit 33715c7

Please sign in to comment.