Skip to content

FFM-11115 - Update Evaluation logic to be more efficient #147

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

Merged
merged 2 commits into from
Apr 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion analyticsservice/analytics.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const (
variationValueAttribute string = "featureValue"
targetAttribute string = "target"
sdkVersionAttribute string = "SDK_VERSION"
SdkVersion string = "0.1.19"
SdkVersion string = "0.1.20"
sdkTypeAttribute string = "SDK_TYPE"
sdkType string = "server"
sdkLanguageAttribute string = "SDK_LANGUAGE"
Expand Down
50 changes: 18 additions & 32 deletions evaluation/evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,58 +85,44 @@ func NewEvaluator(query Query, postEvalCallback PostEvaluateCallback, logger log
}

func (e Evaluator) evaluateClause(clause *rest.Clause, target *Target) bool {
if clause == nil {
return false
}

values := clause.Values
if len(values) == 0 {
return false
}
value := values[0]

operator := clause.Op
if operator == "" {
e.logger.Warnf("Clause has no valid operator: Clause (%v)", clause)
if clause == nil || len(clause.Values) == 0 || clause.Op == "" {
e.logger.Debugf("Clause cannot be evaluated because operator is either nil, has no values or operation: Clause (%v)", clause)
return false
}

value := clause.Values[0]
attrValue := getAttrValue(target, clause.Attribute)
if operator != segmentMatchOperator && !attrValue.IsValid() {
e.logger.Debugf("Operator is not a segment match and attribute value is not valid: Operator (%s), attributeVal (%s)", operator, attrValue.String())

if clause.Op != segmentMatchOperator && attrValue == "" {
e.logger.Debugf("Operator is not a segment match and attribute value is not valid: Operator (%s), attributeVal (%s)", clause.Op, attrValue)
return false
}

object := reflectValueToString(attrValue)

switch operator {
switch clause.Op {
case startsWithOperator:
return strings.HasPrefix(object, value)
return strings.HasPrefix(attrValue, value)
case endsWithOperator:
return strings.HasSuffix(object, value)
return strings.HasSuffix(attrValue, value)
case matchOperator:
found, err := regexp.MatchString(value, object)
if err != nil || !found {
return false
}
return true
found, err := regexp.MatchString(value, attrValue)
return err == nil && found
case containsOperator:
return strings.Contains(object, value)
return strings.Contains(attrValue, value)
case equalOperator:
return strings.EqualFold(object, value)
return strings.EqualFold(attrValue, value)
case equalSensitiveOperator:
return object == value
return attrValue == value
case inOperator:
for _, val := range values {
if val == object {
for _, val := range clause.Values {
if val == attrValue {
return true
}
}
return false
case gtOperator:
return object > value
return attrValue > value
case segmentMatchOperator:
return e.isTargetIncludedOrExcludedInSegment(values, target)
return e.isTargetIncludedOrExcludedInSegment(clause.Values, target)
default:
return false
}
Expand Down
252 changes: 252 additions & 0 deletions evaluation/evaluator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1799,3 +1799,255 @@ func TestEvaluator_JSONVariation(t *testing.T) {
})
}
}

// BENCHMARK
func BenchmarkEvaluateClause_NilClause(b *testing.B) {
evaluator := Evaluator{}
var clause *rest.Clause = nil
target := &Target{
Identifier: "harness",
}
for i := 0; i < b.N; i++ {
evaluator.evaluateClause(clause, target)
}
}

func BenchmarkEvaluateClause_EmptyOperator(b *testing.B) {
evaluator := Evaluator{}
clause := &rest.Clause{
Op: "",
Values: []string{"harness"},
}
for i := 0; i < b.N; i++ {
evaluator.evaluateClause(clause, nil)
}
}

func BenchmarkEvaluateClause_NilValues(b *testing.B) {
evaluator := Evaluator{}
clause := &rest.Clause{
Values: nil,
}
for i := 0; i < b.N; i++ {
evaluator.evaluateClause(clause, nil)
}
}

func BenchmarkEvaluateClause_EmptyValues(b *testing.B) {
evaluator := Evaluator{}
clause := &rest.Clause{
Values: []string{},
}
for i := 0; i < b.N; i++ {
evaluator.evaluateClause(clause, nil)
}
}

func BenchmarkEvaluateClause_WrongOperator(b *testing.B) {
evaluator := Evaluator{}
clause := &rest.Clause{
Attribute: "identifier",
Op: "greaterthan",
Values: []string{"harness"},
}
target := &Target{
Identifier: "harness",
}
for i := 0; i < b.N; i++ {
evaluator.evaluateClause(clause, target)
}
}

func BenchmarkEvaluateClause_EmptyAttribute(b *testing.B) {
evaluator := Evaluator{}
clause := &rest.Clause{
Attribute: "",
Op: "equalOperator",
Values: []string{"harness"},
}
target := &Target{
Identifier: "harness",
}
for i := 0; i < b.N; i++ {
evaluator.evaluateClause(clause, target)
}
}

func BenchmarkEvaluateClause_MatchOperator(b *testing.B) {
evaluator := Evaluator{}
clause := &rest.Clause{
Attribute: "identifier",
Op: "matchOperator",
Values: []string{"^harness$"},
}
target := &Target{
Identifier: "harness",
}
for i := 0; i < b.N; i++ {
evaluator.evaluateClause(clause, target)
}
}

func BenchmarkEvaluateClause_MatchOperatorError(b *testing.B) {
evaluator := Evaluator{}
clause := &rest.Clause{
Attribute: "identifier",
Op: "matchOperator",
Values: []string{"^harness(wings$"},
}
target := &Target{
Identifier: "harness",
}
for i := 0; i < b.N; i++ {
evaluator.evaluateClause(clause, target)
}
}

func BenchmarkEvaluateClause_InOperator(b *testing.B) {
evaluator := Evaluator{}
clause := &rest.Clause{
Attribute: "identifier",
Op: "inOperator",
Values: []string{"harness", "wings-software"},
}
target := &Target{
Identifier: "harness",
}
for i := 0; i < b.N; i++ {
evaluator.evaluateClause(clause, target)
}
}

func BenchmarkEvaluateClause_InOperatorNotFound(b *testing.B) {
evaluator := Evaluator{}
clause := &rest.Clause{
Attribute: "identifier",
Op: "inOperator",
Values: []string{"harness1", "wings-software"},
}
target := &Target{
Identifier: "harness",
}
for i := 0; i < b.N; i++ {
evaluator.evaluateClause(clause, target)
}
}

func BenchmarkEvaluateClause_EqualOperator(b *testing.B) {
evaluator := Evaluator{}
clause := &rest.Clause{
Attribute: "identifier",
Op: "equalOperator",
Values: []string{"harness"},
}
target := &Target{
Identifier: "harness",
}
for i := 0; i < b.N; i++ {
evaluator.evaluateClause(clause, target)
}
}

func BenchmarkEvaluateClause_EqualSensitiveOperator(b *testing.B) {
evaluator := Evaluator{}
clause := &rest.Clause{
Attribute: "identifier",
Op: "equalSensitiveOperator",
Values: []string{"harness"},
}
target := &Target{
Identifier: "Harness",
}
for i := 0; i < b.N; i++ {
evaluator.evaluateClause(clause, target)
}
}

func BenchmarkEvaluateClause_GTOperator(b *testing.B) {
evaluator := Evaluator{}
clause := &rest.Clause{
Attribute: "identifier",
Op: "gtOperator",
Values: []string{"A"},
}
target := &Target{
Identifier: "B",
}
for i := 0; i < b.N; i++ {
evaluator.evaluateClause(clause, target)
}
}

func BenchmarkEvaluateClause_GTOperatorNegative(b *testing.B) {
evaluator := Evaluator{}
clause := &rest.Clause{
Attribute: "identifier",
Op: "gtOperator",
Values: []string{"B"},
}
target := &Target{
Identifier: "A",
}
for i := 0; i < b.N; i++ {
evaluator.evaluateClause(clause, target)
}
}

func BenchmarkEvaluateClause_StartsWithOperator(b *testing.B) {
evaluator := Evaluator{}
clause := &rest.Clause{
Attribute: "identifier",
Op: "startsWithOperator",
Values: []string{"harness"},
}
target := &Target{
Identifier: "harness - wings software",
}
for i := 0; i < b.N; i++ {
evaluator.evaluateClause(clause, target)
}
}

func BenchmarkEvaluateClause_EndsWithOperator(b *testing.B) {
evaluator := Evaluator{}
clause := &rest.Clause{
Attribute: "identifier",
Op: "endsWithOperator",
Values: []string{"harness"},
}
target := &Target{
Identifier: "wings software - harness",
}
for i := 0; i < b.N; i++ {
evaluator.evaluateClause(clause, target)
}
}

func BenchmarkEvaluateClause_ContainsOperator(b *testing.B) {
evaluator := Evaluator{}
clause := &rest.Clause{
Attribute: "identifier",
Op: "containsOperator",
Values: []string{"harness"},
}
target := &Target{
Identifier: "wings harness software",
}
for i := 0; i < b.N; i++ {
evaluator.evaluateClause(clause, target)
}
}

func BenchmarkEvaluateClause_SegmentMatchOperator(b *testing.B) {
evaluator := Evaluator{query: testRepo}
clause := &rest.Clause{
Op: "segmentMatchOperator",
Values: []string{"beta"},
}
target := &Target{
Identifier: "harness",
}
for i := 0; i < b.N; i++ {
evaluator.evaluateClause(clause, target)
}
}
Loading
Loading