Skip to content

Commit

Permalink
FFM-11115 - Update Evaluation logic to be more efficient (#147)
Browse files Browse the repository at this point in the history
* Update Evaluation logic to be more efficient
  • Loading branch information
stephenmcconkey authored Apr 9, 2024
1 parent cfb1894 commit 1780158
Show file tree
Hide file tree
Showing 5 changed files with 317 additions and 217 deletions.
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

0 comments on commit 1780158

Please sign in to comment.