Skip to content

Commit

Permalink
Simplify EvaluateContext + improve perf. of user attribute retrieval …
Browse files Browse the repository at this point in the history
…during evaluation + reduce allocations
  • Loading branch information
adams85 committed Feb 5, 2024
1 parent fd11295 commit af8212d
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 24 deletions.
18 changes: 4 additions & 14 deletions src/ConfigCatClient/Evaluation/EvaluateContext.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using ConfigCat.Client.Utils;

namespace ConfigCat.Client.Evaluation;
Expand All @@ -8,16 +7,9 @@ internal struct EvaluateContext
{
public readonly string Key;
public readonly Setting Setting;
public readonly User? User;
public readonly IReadOnlyDictionary<string, Setting> Settings;

private readonly User? user;

[MemberNotNullWhen(true, nameof(UserAttributes))]
public readonly bool IsUserAvailable => this.user is not null;

private IReadOnlyDictionary<string, object>? userAttributes;
public IReadOnlyDictionary<string, object>? UserAttributes => this.userAttributes ??= this.user?.GetAllAttributes();

private List<string>? visitedFlags;
public List<string> VisitedFlags => this.visitedFlags ??= new List<string>();

Expand All @@ -30,19 +22,17 @@ public EvaluateContext(string key, Setting setting, User? user, IReadOnlyDiction
{
this.Key = key;
this.Setting = setting;
this.user = user;
this.User = user;
this.Settings = settings;

this.userAttributes = null;
this.visitedFlags = null;
this.IsMissingUserObjectLogged = this.IsMissingUserObjectAttributeLogged = false;
this.LogBuilder = null; // initialized by RolloutEvaluator.Evaluate
}

public EvaluateContext(string key, Setting setting, ref EvaluateContext dependentFlagContext)
: this(key, setting, dependentFlagContext.user, dependentFlagContext.Settings)
public EvaluateContext(string key, Setting setting, in EvaluateContext dependentFlagContext)
: this(key, setting, dependentFlagContext.User, dependentFlagContext.Settings)
{
this.userAttributes = dependentFlagContext.UserAttributes;
this.visitedFlags = dependentFlagContext.VisitedFlags; // crucial to use the property here to make sure the list is created!
this.LogBuilder = dependentFlagContext.LogBuilder;
}
Expand Down
32 changes: 22 additions & 10 deletions src/ConfigCatClient/Evaluation/RolloutEvaluator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Text;
using ConfigCat.Client.Utils;
using ConfigCat.Client.Versioning;
Expand Down Expand Up @@ -35,9 +34,9 @@ public EvaluateResult Evaluate<T>(T defaultValue, ref EvaluateContext context, [

logBuilder.Append($"Evaluating '{context.Key}'");

if (context.IsUserAvailable)
if (context.User is not null)
{
logBuilder.Append($" for User '{context.UserAttributes.Serialize()}'");
logBuilder.Append($" for User '{context.User.GetAllAttributes().Serialize()}'");
}

logBuilder.IncreaseIndent();
Expand Down Expand Up @@ -178,7 +177,7 @@ private bool TryEvaluatePercentageOptions(PercentageOption[] percentageOptions,
{
var logBuilder = context.LogBuilder;

if (!context.IsUserAvailable)
if (context.User is null)
{
logBuilder?.NewLine("Skipping % options because the User Object is missing.");

Expand All @@ -192,9 +191,21 @@ private bool TryEvaluatePercentageOptions(PercentageOption[] percentageOptions,
return false;
}

var percentageOptionsAttributeName = context.Setting.PercentageOptionsAttribute ?? nameof(User.Identifier);
string percentageOptionsAttributeName;
object? percentageOptionsAttributeValue;

if (!context.UserAttributes.TryGetValue(percentageOptionsAttributeName, out var percentageOptionsAttributeValue))
if (context.Setting.PercentageOptionsAttribute is null)
{
percentageOptionsAttributeName = nameof(User.Identifier);
percentageOptionsAttributeValue = context.User.Identifier;
}
else
{
percentageOptionsAttributeName = context.Setting.PercentageOptionsAttribute;
percentageOptionsAttributeValue = context.User.GetAttribute(percentageOptionsAttributeName);
}

if (percentageOptionsAttributeValue is null)
{
logBuilder?.NewLine().Append($"Skipping % options because the User.{percentageOptionsAttributeName} attribute is missing.");

Expand Down Expand Up @@ -334,7 +345,7 @@ private bool EvaluateUserCondition(UserCondition condition, string contextSalt,
var logBuilder = context.LogBuilder;
logBuilder?.AppendUserCondition(condition);

if (!context.IsUserAvailable)
if (context.User is null)
{
if (!context.IsMissingUserObjectLogged)
{
Expand All @@ -347,8 +358,9 @@ private bool EvaluateUserCondition(UserCondition condition, string contextSalt,
}

var userAttributeName = condition.ComparisonAttribute ?? throw new InvalidOperationException("Comparison attribute name is missing.");
var userAttributeValue = context.User.GetAttribute(userAttributeName);

if (!context.UserAttributes.TryGetValue(userAttributeName, out var userAttributeValue) || userAttributeValue is string { Length: 0 })
if (userAttributeValue is null || userAttributeValue is string { Length: 0 })
{
this.logger.UserObjectAttributeIsMissing(condition.ToString(), context.Key, userAttributeName);
error = string.Format(CultureInfo.InvariantCulture, MissingUserAttributeError, userAttributeName);
Expand Down Expand Up @@ -723,7 +735,7 @@ private bool EvaluatePrerequisiteFlagCondition(PrerequisiteFlagCondition conditi
throw new InvalidOperationException($"Circular dependency detected between the following depending flags: {dependencyCycle}.");
}

var prerequisiteFlagContext = new EvaluateContext(prerequisiteFlagKey!, prerequisiteFlag!, ref context);
var prerequisiteFlagContext = new EvaluateContext(prerequisiteFlagKey!, prerequisiteFlag!, context);

logBuilder?
.NewLine("(")
Expand Down Expand Up @@ -762,7 +774,7 @@ private bool EvaluateSegmentCondition(SegmentCondition condition, ref EvaluateCo
var logBuilder = context.LogBuilder;
logBuilder?.AppendSegmentCondition(condition);

if (!context.IsUserAvailable)
if (context.User is null)
{
if (!context.IsMissingUserObjectLogged)
{
Expand Down
12 changes: 12 additions & 0 deletions src/ConfigCatClient/User.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,18 @@ public IDictionary<string, object> Custom
get => this.custom ??= new Dictionary<string, object>();
set => this.custom = value;
}

internal object? GetAttribute(string name)
{
return name switch
{
nameof(Identifier) => Identifier,
nameof(Email) => Email,
nameof(Country) => Country,
_ => this.custom is not null && this.custom.TryGetValue(name, out var value) ? value : null
};
}

/// <summary>
/// Returns all attributes of the user.
/// </summary>
Expand Down

0 comments on commit af8212d

Please sign in to comment.