Skip to content

Commit

Permalink
Added Fusion Cost Analyzer Improvements. (#7867)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelstaib authored Dec 24, 2024
1 parent f035d42 commit d8439f1
Show file tree
Hide file tree
Showing 22 changed files with 682 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,22 @@ internal override void InitializeContext(
}
}

internal override bool SkipDirectiveDefinition(DirectiveDefinitionNode node)
{
ref var first = ref GetReference();
var length = _typeInterceptors.Length;

for (var i = 0; i < length; i++)
{
if (Unsafe.Add(ref first, i).SkipDirectiveDefinition(node))
{
return true;
}
}

return false;
}

public override void OnBeforeDiscoverTypes()
{
ref var first = ref GetReference();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ public virtual void OnBeforeDiscoverTypes() { }
/// </summary>
public virtual void OnAfterDiscoverTypes() { }

internal virtual bool SkipDirectiveDefinition(DirectiveDefinitionNode node)
=> false;

/// <summary>
/// This event is triggered after the type instance was created but before
/// any type definition was initialized.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,11 @@ protected override ISyntaxVisitorAction VisitChildren(
goto EXIT;
}

if(context.DescriptorContext.TypeInterceptor.SkipDirectiveDefinition(node))
{
goto EXIT;
}

context.Types.Add(
TypeReference.Create(
_directiveTypeFactory.Create(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public async ValueTask InvokeAsync(IRequestContext context)
}

var requestOptions = context.TryGetCostOptions() ?? options;
var mode = context.GetCostAnalyzerMode(requestOptions.EnforceCostLimits);
var mode = context.GetCostAnalyzerMode(requestOptions);

if (mode == CostAnalyzerMode.Skip)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,13 @@ public override void OnBeforeCompleteType(ITypeCompletionContext completionConte
}
}

internal sealed class CostDirectiveTypeInterceptor : TypeInterceptor
{
internal override bool SkipDirectiveDefinition(DirectiveDefinitionNode node)
=> node.Name.Value.Equals("cost", StringComparison.Ordinal)
|| node.Name.Value.Equals("listSize", StringComparison.Ordinal);
}

file static class Extensions
{
public static bool HasCostDirective(this IHasDirectiveDefinition directiveProvider)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,14 @@ public static IRequestExecutorBuilder AddCostAnalyzer(this IRequestExecutorBuild
requestOptions.MaxFieldCost,
requestOptions.MaxTypeCost,
requestOptions.EnforceCostLimits,
requestOptions.SkipAnalyzer,
requestOptions.Filtering.VariableMultiplier);
});
})
.AddDirectiveType<CostDirectiveType>()
.AddDirectiveType<ListSizeDirectiveType>()
.TryAddTypeInterceptor<CostTypeInterceptor>()
.TryAddTypeInterceptor<CostDirectiveTypeInterceptor>()

// we are replacing the default pipeline if the cost analyzer is added.
.Configure(c => c.DefaultPipelineFactory = AddDefaultPipeline);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,21 +58,26 @@ public static CostMetrics GetCostMetrics(

internal static CostAnalyzerMode GetCostAnalyzerMode(
this IRequestContext context,
bool enforceCostLimits)
RequestCostOptions options)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}

if (options.SkipAnalyzer)
{
return CostAnalyzerMode.Skip;
}

if (context.ContextData.ContainsKey(WellKnownContextData.ValidateCost))
{
return CostAnalyzerMode.Analyze | CostAnalyzerMode.Report;
}

var flags = CostAnalyzerMode.Analyze;

if (enforceCostLimits)
if (options.EnforceCostLimits)
{
flags |= CostAnalyzerMode.Enforce;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

<ItemGroup>
<InternalsVisibleTo Include="HotChocolate.CostAnalysis.Tests" />
<InternalsVisibleTo Include="HotChocolate.Fusion" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ namespace HotChocolate.CostAnalysis;
/// </summary>
public sealed class CostOptions
{
private bool _skipAnalyzer = false;
private bool _enforceCostLimits = true;

/// <summary>
/// Gets or sets the maximum allowed field cost.
/// </summary>
Expand All @@ -18,7 +21,35 @@ public sealed class CostOptions
/// <summary>
/// Defines if the analyzer shall enforce cost limits.
/// </summary>
public bool EnforceCostLimits { get; set; } = true;
public bool EnforceCostLimits
{
get => _enforceCostLimits;
set
{
if(value)
{
SkipAnalyzer = false;
}

_enforceCostLimits = value;
}
}

/// <summary>
/// Skips the cost analyzer.
/// </summary>
public bool SkipAnalyzer
{
get => _skipAnalyzer;
set
{
if(value)
{
EnforceCostLimits = false;
}
_skipAnalyzer = value;
}
}

/// <summary>
/// Defines if cost defaults shall be applied to the schema.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,145 @@ namespace HotChocolate.CostAnalysis;
/// <summary>
/// Request options for cost analysis.
/// </summary>
/// <param name="MaxFieldCost">
/// The maximum allowed field cost.
/// </param>
/// <param name="MaxTypeCost">
/// The maximum allowed type cost.
/// </param>
/// <param name="EnforceCostLimits">
/// Defines if the analyzer shall enforce cost limits.
/// </param>
/// <param name="FilterVariableMultiplier">
/// The filter variable multiplier.
/// </param>
public record RequestCostOptions(
double MaxFieldCost,
double MaxTypeCost,
bool EnforceCostLimits,
int? FilterVariableMultiplier);
public record RequestCostOptions
{
/// <summary>
/// Request options for cost analysis.
/// </summary>
/// <param name="maxFieldCost">
/// The maximum allowed field cost.
/// </param>
/// <param name="maxTypeCost">
/// The maximum allowed type cost.
/// </param>
/// <param name="enforceCostLimits">
/// Defines if the analyzer shall enforce cost limits.
/// </param>
/// <param name="filterVariableMultiplier">
/// The filter variable multiplier.
/// </param>
public RequestCostOptions(
double maxFieldCost,
double maxTypeCost,
bool enforceCostLimits,
int? filterVariableMultiplier)
{
MaxFieldCost = maxFieldCost;
MaxTypeCost = maxTypeCost;
EnforceCostLimits = enforceCostLimits;
FilterVariableMultiplier = filterVariableMultiplier;
}

/// <summary>
/// Gets the maximum allowed field cost.
/// </summary>
/// <param name="maxFieldCost">
/// The maximum allowed field cost.
/// </param>
/// <param name="maxTypeCost">
/// The maximum allowed type cost.
/// </param>
/// <param name="enforceCostLimits">
/// Defines if the analyzer shall enforce cost limits.
/// </param>
/// <param name="skipAnalyzer">
/// Defines if the cost analyzer shall be skipped.
/// </param>
/// <param name="filterVariableMultiplier">
/// The filter variable multiplier.
/// </param>
public RequestCostOptions(
double maxFieldCost,
double maxTypeCost,
bool enforceCostLimits,
bool skipAnalyzer,
int? filterVariableMultiplier)
{
MaxFieldCost = maxFieldCost;
MaxTypeCost = maxTypeCost;
EnforceCostLimits = enforceCostLimits;
SkipAnalyzer = skipAnalyzer;
FilterVariableMultiplier = filterVariableMultiplier;
}

/// <summary>
/// Gets the maximum allowed field cost.
/// </summary>
public double MaxFieldCost { get; init; }

/// <summary>
/// Gets the maximum allowed type cost.
/// </summary>
public double MaxTypeCost { get; init; }

/// <summary>
/// Defines if the analyzer shall enforce cost limits.
/// </summary>
public bool EnforceCostLimits
{
get;
init
{
if (value)
{
SkipAnalyzer = false;
}

field = value;
}
}

/// <summary>
/// Defines if the cost analyzer shall be skipped.
/// </summary>
public bool SkipAnalyzer
{
get;
init
{
if (value)
{
EnforceCostLimits = false;
}

field = value;
}
}

/// <summary>
/// Gets the filter variable multiplier.
/// </summary>
public int? FilterVariableMultiplier { get; init; }

/// <summary>
/// Deconstructs the request options.
/// </summary>
/// <param name="maxFieldCost">
/// The maximum allowed field cost.
/// </param>
/// <param name="maxTypeCost">
/// The maximum allowed type cost.
/// </param>
/// <param name="enforceCostLimits">
/// Defines if the analyzer shall enforce cost limits.
/// </param>
/// <param name="skipAnalyzer">
/// Defines if the cost analyzer shall be skipped.
/// </param>
/// <param name="filterVariableMultiplier">
/// The filter variable multiplier.
/// </param>
public void Deconstruct(
out double maxFieldCost,
out double maxTypeCost,
out bool enforceCostLimits,
out bool skipAnalyzer,
out int? filterVariableMultiplier)
{
maxFieldCost = MaxFieldCost;
maxTypeCost = MaxTypeCost;
enforceCostLimits = EnforceCostLimits;
skipAnalyzer = SkipAnalyzer;
filterVariableMultiplier = FilterVariableMultiplier;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using HotChocolate.Language;
using HotChocolate.Skimmed;
using HotChocolate.Types;
using HotChocolate.Utilities;

namespace HotChocolate.Fusion.Composition;

Expand Down Expand Up @@ -189,6 +191,25 @@ internal static void MergeDirectivesWith(
}
else
{
if (directive.Name.EqualsOrdinal("cost"))
{
var currentCost = target.Directives.FirstOrDefault("cost")!;
if (currentCost.Arguments.TryGetValue("weight", out var value)
&& value is StringValueNode stringValueNode
&& double.TryParse(stringValueNode.Value, out var currentWeight)
&& directive.Arguments.TryGetValue("weight", out value)
&& value is StringValueNode newStringValueNode
&& double.TryParse(newStringValueNode.Value, out var newWeight)
&& newWeight > currentWeight)
{
target.Directives.Remove(currentCost);
target.Directives.Add(directive);
}

continue;
}


if (directiveDefinition is not null && directiveDefinition.IsRepeatable)
{
target.Directives.Add(directive);
Expand Down
Loading

0 comments on commit d8439f1

Please sign in to comment.