Skip to content

Commit 2155124

Browse files
authored
Ported source-schemas from the guild benchmarks (#8875)
1 parent 7f7e6ad commit 2155124

File tree

80 files changed

+2611
-83
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+2611
-83
lines changed

.github/workflows/benchmarks.yml

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,187 @@ concurrency:
1515
cancel-in-progress: true
1616

1717
jobs:
18+
fusion-gateway:
19+
name: "Fusion Gateway Benchmarks"
20+
if: github.event_name == 'push' || github.event.pull_request.draft == false
21+
runs-on: benchmarking
22+
permissions:
23+
contents: write
24+
pull-requests: write
25+
26+
steps:
27+
- name: Checkout current repository
28+
uses: actions/checkout@v4
29+
with:
30+
fetch-depth: 0
31+
show-progress: false
32+
33+
- name: Checkout performance data repository
34+
uses: actions/checkout@v4
35+
with:
36+
repository: ChilliCream/graphql-platform-performance-data
37+
token: ${{ secrets.PERFORMANCE_DATA_TOKEN }}
38+
path: performance-data-repo
39+
fetch-depth: 0
40+
show-progress: false
41+
42+
- name: Install k6
43+
run: |
44+
if ! command -v k6 &> /dev/null; then
45+
echo "Installing k6..."
46+
sudo gpg -k
47+
sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
48+
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
49+
sudo apt-get update
50+
sudo apt-get install k6 -y
51+
fi
52+
k6 version
53+
54+
- name: Install jq for result parsing
55+
run: |
56+
if ! command -v jq &> /dev/null; then
57+
sudo apt-get install jq -y
58+
fi
59+
60+
- name: Make scripts executable
61+
working-directory: src/HotChocolate/Fusion-vnext/benchmarks/k6
62+
run: chmod +x *.sh
63+
64+
- name: Run Fusion benchmarks
65+
working-directory: src/HotChocolate/Fusion-vnext/benchmarks/k6
66+
run: |
67+
echo "Starting Fusion Gateway benchmarks..."
68+
./run-benchmarks.sh 2>&1 | tee /tmp/fusion-benchmark.log
69+
70+
- name: Generate performance report
71+
if: always()
72+
working-directory: src/HotChocolate/Fusion-vnext/benchmarks/k6
73+
run: |
74+
# Create a markdown report from the results
75+
cat > fusion-performance-report.md << 'EOF'
76+
# Fusion Gateway Performance Report
77+
78+
## Test Results
79+
80+
### Single Fetch Test (Simple Query)
81+
82+
EOF
83+
84+
# Add single fetch results if they exist
85+
if [ -f results/summary-aot-single.json ] && [ -f results/summary-release-single.json ]; then
86+
echo "#### AOT Mode" >> fusion-performance-report.md
87+
echo '```' >> fusion-performance-report.md
88+
jq -r '.metrics.http_req_duration | "p50: \(.values."p(50)")ms, p95: \(.values."p(95)")ms, p99: \(.values."p(99)")ms"' results/summary-aot-single.json >> fusion-performance-report.md
89+
echo '```' >> fusion-performance-report.md
90+
echo "" >> fusion-performance-report.md
91+
92+
echo "#### Release Mode" >> fusion-performance-report.md
93+
echo '```' >> fusion-performance-report.md
94+
jq -r '.metrics.http_req_duration | "p50: \(.values."p(50)")ms, p95: \(.values."p(95)")ms, p99: \(.values."p(99)")ms"' results/summary-release-single.json >> fusion-performance-report.md
95+
echo '```' >> fusion-performance-report.md
96+
echo "" >> fusion-performance-report.md
97+
fi
98+
99+
echo "### No Recursion Test (Complex Query)" >> fusion-performance-report.md
100+
echo "" >> fusion-performance-report.md
101+
102+
# Add no recursion results if they exist
103+
if [ -f results/summary-aot-no-recursion.json ] && [ -f results/summary-release-no-recursion.json ]; then
104+
echo "#### AOT Mode" >> fusion-performance-report.md
105+
echo '```' >> fusion-performance-report.md
106+
jq -r '.metrics.http_req_duration | "p50: \(.values."p(50)")ms, p95: \(.values."p(95)")ms, p99: \(.values."p(99)")ms"' results/summary-aot-no-recursion.json >> fusion-performance-report.md
107+
echo '```' >> fusion-performance-report.md
108+
echo "" >> fusion-performance-report.md
109+
110+
echo "#### Release Mode" >> fusion-performance-report.md
111+
echo '```' >> fusion-performance-report.md
112+
jq -r '.metrics.http_req_duration | "p50: \(.values."p(50)")ms, p95: \(.values."p(95)")ms, p99: \(.values."p(99)")ms"' results/summary-release-no-recursion.json >> fusion-performance-report.md
113+
echo '```' >> fusion-performance-report.md
114+
echo "" >> fusion-performance-report.md
115+
fi
116+
117+
# Run the comparison script if available
118+
if [ -x compare-results.sh ]; then
119+
echo "## AOT vs Release Comparison" >> fusion-performance-report.md
120+
echo "" >> fusion-performance-report.md
121+
echo '```' >> fusion-performance-report.md
122+
./compare-results.sh >> fusion-performance-report.md 2>&1 || true
123+
echo '```' >> fusion-performance-report.md
124+
fi
125+
126+
cat fusion-performance-report.md
127+
128+
- name: Comment PR with performance report
129+
if: github.event_name == 'pull_request'
130+
uses: actions/github-script@v7
131+
with:
132+
github-token: ${{ secrets.GITHUB_TOKEN }}
133+
script: |
134+
const fs = require('fs');
135+
const reportPath = 'src/HotChocolate/Fusion-vnext/benchmarks/k6/fusion-performance-report.md';
136+
137+
let report;
138+
try {
139+
report = fs.readFileSync(reportPath, 'utf8');
140+
} catch (error) {
141+
console.log('No performance report found, skipping comment');
142+
return;
143+
}
144+
145+
const timestamp = new Date().toUTCString();
146+
const commitSha = context.sha.substring(0, 7);
147+
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
148+
149+
const commentBody = `${report}\n\n---\n*Run [${context.runId}](${runUrl}) • Commit ${commitSha} • ${timestamp}*`;
150+
151+
await github.rest.issues.createComment({
152+
owner: context.repo.owner,
153+
repo: context.repo.repo,
154+
issue_number: context.issue.number,
155+
body: commentBody,
156+
});
157+
158+
- name: Upload performance data as artifact
159+
uses: actions/upload-artifact@v4
160+
if: always()
161+
with:
162+
name: fusion-performance-data
163+
path: |
164+
src/HotChocolate/Fusion-vnext/benchmarks/k6/results/*.json
165+
src/HotChocolate/Fusion-vnext/benchmarks/k6/fusion-performance-report.md
166+
/tmp/fusion-benchmark.log
167+
retention-days: 30
168+
169+
- name: Store performance data to external repository
170+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
171+
working-directory: performance-data-repo
172+
run: |
173+
# Create directory for Fusion data if it doesn't exist
174+
mkdir -p fusion-gateway
175+
176+
# Copy the new performance data
177+
if [ -d ../src/HotChocolate/Fusion-vnext/benchmarks/k6/results ]; then
178+
cp -r ../src/HotChocolate/Fusion-vnext/benchmarks/k6/results/* fusion-gateway/
179+
fi
180+
181+
# Configure git
182+
git config user.name "github-actions[bot]"
183+
git config user.email "github-actions[bot]@users.noreply.github.com"
184+
185+
# Add and commit the performance data
186+
git add fusion-gateway/
187+
188+
# Only commit if there are changes
189+
if ! git diff --staged --quiet; then
190+
git commit -m "Update Fusion Gateway performance data from ${{ github.sha }}"
191+
git push
192+
else
193+
echo "No changes to Fusion performance data"
194+
fi
195+
18196
hotchocolate-core:
19197
name: "HotChocolate Core Benchmarks"
198+
needs: fusion-gateway
20199
if: github.event_name == 'push' || github.event.pull_request.draft == false
21200
runs-on: benchmarking
22201
permissions:

src/All.slnx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@
3737
<Folder Name="/HotChocolate/AspNetCore/" />
3838
<Folder Name="/HotChocolate/AspNetCore/benchmarks/" />
3939
<Folder Name="/HotChocolate/AspNetCore/benchmarks/k6/">
40-
<Project Path="HotChocolate/AspNetCore/benchmarks/k6/Catalog.AppHost/eShop.Catalog.AppHost.csproj" />
4140
<Project Path="HotChocolate/AspNetCore/benchmarks/k6/Catalog.API/eShop.Catalog.API.csproj" />
41+
<Project Path="HotChocolate/AspNetCore/benchmarks/k6/Catalog.AppHost/eShop.Catalog.AppHost.csproj" />
4242
<Project Path="HotChocolate/AspNetCore/benchmarks/k6/Catalog.ServiceDefaults/eShop.Catalog.ServiceDefaults.csproj" />
4343
</Folder>
4444
<Folder Name="/HotChocolate/AspNetCore/src/">
@@ -185,6 +185,13 @@
185185
<Folder Name="/HotChocolate/Fusion-vnext/benchmarks/">
186186
<Project Path="HotChocolate/Fusion-vnext/benchmarks/Fusion.Execution.Benchmarks/Fusion.Execution.Benchmarks.csproj" />
187187
</Folder>
188+
<Folder Name="/HotChocolate/Fusion-vnext/benchmarks/k6/">
189+
<Project Path="HotChocolate/Fusion-vnext/benchmarks/k6/eShop.Gateway/eShop.Gateway.csproj" />
190+
<Project Path="HotChocolate\Fusion-vnext\benchmarks\k6\eShop.Accounts\eShop.Accounts.csproj" />
191+
<Project Path="HotChocolate\Fusion-vnext\benchmarks\k6\eShop.Inventory\eShop.Inventory.csproj" />
192+
<Project Path="HotChocolate\Fusion-vnext\benchmarks\k6\eShop.Products\eShop.Products.csproj" />
193+
<Project Path="HotChocolate\Fusion-vnext\benchmarks\k6\eShop.Reviews\eShop.Reviews.csproj" />
194+
</Folder>
188195
<Folder Name="/HotChocolate/Fusion-vnext/src/">
189196
<Project Path="HotChocolate/Fusion-vnext/src/Fusion.Aspire/HotChocolate.Fusion.Aspire.csproj" />
190197
<Project Path="HotChocolate/Fusion-vnext/src/Fusion.AspNetCore/HotChocolate.Fusion.AspNetCore.csproj" />

src/HotChocolate/AspNetCore/benchmarks/k6/dataloader.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export const options = {
66
stages: [
77
{ duration: '10s', target: 50 },
88
{ duration: '20s', target: 500 },
9-
{ duration: '1m', target: 500 },
9+
{ duration: '10s', target: 500 },
1010
],
1111
thresholds: {
1212
'http_req_duration{phase:measurement}': ['p(95)<500', 'p(99)<1000'],

src/HotChocolate/AspNetCore/benchmarks/k6/single-fetch.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export const options = {
66
stages: [
77
{ duration: '10s', target: 50 },
88
{ duration: '20s', target: 500 },
9-
{ duration: '1m', target: 500 },
9+
{ duration: '10s', target: 500 },
1010
],
1111
thresholds: {
1212
'http_req_duration{phase:measurement}': ['p(95)<500', 'p(99)<1000'],

src/HotChocolate/Core/src/Execution.Abstractions/Execution/ExecutionResult.cs

Lines changed: 54 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Buffers;
12
using HotChocolate.Features;
23

34
namespace HotChocolate.Execution;
@@ -7,16 +8,23 @@ namespace HotChocolate.Execution;
78
/// </summary>
89
public abstract class ExecutionResult : IExecutionResult
910
{
10-
private Func<ValueTask>[] _cleanupTasks = [];
11+
protected static readonly ArrayPool<Func<ValueTask>> CleanUpTaskPool = ArrayPool<Func<ValueTask>>.Shared;
12+
private Func<ValueTask>[] _cleanUpTasks = [];
13+
private int _cleanupTasksLength;
1114
private bool _disposed;
1215

1316
protected ExecutionResult()
1417
{
1518
}
1619

17-
protected ExecutionResult(Func<ValueTask>[] cleanupTasks)
20+
protected ExecutionResult((Func<ValueTask>[] Tasks, int Length) cleanupTasks)
1821
{
19-
_cleanupTasks = cleanupTasks ?? throw new ArgumentNullException(nameof(cleanupTasks));
22+
if (cleanupTasks.Tasks is null)
23+
{
24+
throw new ArgumentNullException(nameof(cleanupTasks));
25+
}
26+
27+
(_cleanUpTasks, _cleanupTasksLength) = cleanupTasks;
2028
}
2129

2230
/// <inheritdoc cref="IExecutionResult" />
@@ -28,34 +36,67 @@ protected ExecutionResult(Func<ValueTask>[] cleanupTasks)
2836
/// <inheritdoc cref="IFeatureProvider" />
2937
public IFeatureCollection Features { get; } = new FeatureCollection();
3038

31-
private protected Func<ValueTask>[] CleanupTasks => _cleanupTasks;
39+
/// <summary>
40+
/// This helper allows someone else to take over the responsibility over the cleanup tasks.
41+
/// This object no longer will track them after they were taken over.
42+
/// </summary>
43+
private protected (Func<ValueTask>[] Tasks, int Length) TakeCleanUpTasks()
44+
{
45+
var tasks = _cleanUpTasks;
46+
var taskLength = _cleanupTasksLength;
47+
48+
_cleanUpTasks = [];
49+
_cleanupTasksLength = 0;
50+
51+
return (tasks, taskLength);
52+
}
3253

3354
/// <inheritdoc cref="IExecutionResult" />
3455
public void RegisterForCleanup(Func<ValueTask> clean)
3556
{
3657
ArgumentNullException.ThrowIfNull(clean);
3758

38-
var length = _cleanupTasks.Length;
39-
Array.Resize(ref _cleanupTasks, length + 1);
40-
_cleanupTasks[length] = clean;
59+
if (_cleanUpTasks.Length == 0)
60+
{
61+
_cleanUpTasks = CleanUpTaskPool.Rent(8);
62+
_cleanupTasksLength = 0;
63+
}
64+
else if (_cleanupTasksLength >= _cleanUpTasks.Length)
65+
{
66+
var buffer = CleanUpTaskPool.Rent(_cleanupTasksLength * 2);
67+
var currentBuffer = _cleanUpTasks.AsSpan();
68+
69+
currentBuffer.CopyTo(buffer);
70+
currentBuffer.Clear();
71+
CleanUpTaskPool.Return(_cleanUpTasks);
72+
73+
_cleanUpTasks = buffer;
74+
}
75+
76+
_cleanUpTasks[_cleanupTasksLength++] = clean;
4177
}
4278

4379
/// <summary>
4480
/// Will throw an <see cref="ObjectDisposedException"/> if the result is already disposed.
4581
/// </summary>
46-
protected void EnsureNotDisposed()
47-
{
48-
ObjectDisposedException.ThrowIf(_disposed, this);
49-
}
82+
protected void EnsureNotDisposed() => ObjectDisposedException.ThrowIf(_disposed, this);
5083

5184
/// <inheritdoc cref="IAsyncDisposable"/>
5285
public async ValueTask DisposeAsync()
5386
{
5487
if (!_disposed)
5588
{
56-
for (var i = 0; i < _cleanupTasks.Length; i++)
89+
if (_cleanupTasksLength > 0)
5790
{
58-
await _cleanupTasks[i].Invoke();
91+
var tasks = _cleanUpTasks;
92+
93+
for (var i = 0; i < _cleanupTasksLength; i++)
94+
{
95+
await tasks[i]().ConfigureAwait(false);
96+
}
97+
98+
tasks.AsSpan(0, _cleanupTasksLength).Clear();
99+
CleanUpTaskPool.Return(tasks);
59100
}
60101

61102
_disposed = true;

src/HotChocolate/Core/src/Execution.Abstractions/Execution/Extensions/CoreExecutionResultExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ public static void RegisterForCleanup(this IExecutionResult result, Action clean
4141
public static void RegisterForCleanup(this IExecutionResult result, IDisposable disposable)
4242
{
4343
ArgumentNullException.ThrowIfNull(result);
44-
ArgumentNullException.ThrowIfNull(disposable);
45-
44+
ArgumentNullException.ThrowIfNull(disposable)
45+
;
4646
result.RegisterForCleanup(disposable.Dispose);
4747
}
4848

src/HotChocolate/Core/src/Execution.Abstractions/Execution/OperationResult.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ internal OperationResult(
1515
string? label,
1616
Path? path,
1717
bool? hasNext,
18-
Func<ValueTask>[] cleanupTasks,
18+
(Func<ValueTask>[] Tasks, int Length) cleanupTasks,
1919
bool isDataSet,
2020
int? requestIndex,
2121
int? variableIndex,
@@ -138,7 +138,8 @@ public OperationResult(
138138
/// </returns>
139139
public OperationResult WithExtensions(
140140
IReadOnlyDictionary<string, object?>? extensions)
141-
=> new OperationResult(
141+
{
142+
return new OperationResult(
142143
data: Data,
143144
errors: Errors,
144145
extensions: extensions,
@@ -148,10 +149,11 @@ public OperationResult WithExtensions(
148149
label: Label,
149150
path: Path,
150151
hasNext: HasNext,
151-
cleanupTasks: CleanupTasks,
152+
cleanupTasks: TakeCleanUpTasks(),
152153
isDataSet: IsDataSet,
153154
requestIndex: RequestIndex,
154155
variableIndex: VariableIndex);
156+
}
155157

156158
/// <summary>
157159
/// Creates a new <see cref="OperationResult"/> with the specified context data.
@@ -174,7 +176,7 @@ public OperationResult WithContextData(
174176
label: Label,
175177
path: Path,
176178
hasNext: HasNext,
177-
cleanupTasks: CleanupTasks,
179+
cleanupTasks: TakeCleanUpTasks(),
178180
isDataSet: IsDataSet,
179181
requestIndex: RequestIndex,
180182
variableIndex: VariableIndex);

0 commit comments

Comments
 (0)