Skip to content

Commit 7547598

Browse files
committed
ws
1 parent 5906de4 commit 7547598

21 files changed

Lines changed: 806 additions & 6 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
FROM mcr.microsoft.com/dotnet/sdk:10.0-preview AS build
2+
WORKDIR /app
3+
COPY . .
4+
RUN dotnet publish -c Release -o out
5+
6+
FROM mcr.microsoft.com/dotnet/aspnet:10.0-preview-alpine
7+
WORKDIR /app
8+
COPY --from=build /app/out .
9+
EXPOSE 8080
10+
ENTRYPOINT ["dotnet", "aspnet-websocket.dll"]
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using System.Net.WebSockets;
2+
using Microsoft.AspNetCore.Server.Kestrel.Core;
3+
4+
var builder = WebApplication.CreateBuilder(args);
5+
builder.Logging.ClearProviders();
6+
7+
builder.WebHost.ConfigureKestrel(options =>
8+
{
9+
options.ListenAnyIP(8080, lo =>
10+
{
11+
lo.Protocols = HttpProtocols.Http1AndHttp2;
12+
});
13+
});
14+
15+
var app = builder.Build();
16+
17+
app.UseWebSockets();
18+
19+
app.Map("/ws", async context =>
20+
{
21+
if (!context.WebSockets.IsWebSocketRequest)
22+
{
23+
context.Response.StatusCode = 400;
24+
await context.Response.WriteAsync("Not a WebSocket request");
25+
return;
26+
}
27+
28+
using var ws = await context.WebSockets.AcceptWebSocketAsync();
29+
var buffer = new byte[4096];
30+
31+
while (true)
32+
{
33+
var result = await ws.ReceiveAsync(buffer, CancellationToken.None);
34+
35+
if (result.MessageType == WebSocketMessageType.Close)
36+
{
37+
await ws.CloseAsync(
38+
WebSocketCloseStatus.NormalClosure,
39+
null,
40+
CancellationToken.None);
41+
break;
42+
}
43+
44+
await ws.SendAsync(
45+
new ArraySegment<byte>(buffer, 0, result.Count),
46+
result.MessageType,
47+
result.EndOfMessage,
48+
CancellationToken.None);
49+
}
50+
});
51+
52+
Console.WriteLine("Application started.");
53+
app.Run();
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
<PropertyGroup>
3+
<TargetFramework>net10.0</TargetFramework>
4+
<ImplicitUsings>enable</ImplicitUsings>
5+
</PropertyGroup>
6+
</Project>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"display_name": "aspnet-websocket",
3+
"language": "C#",
4+
"type": "framework",
5+
"engine": "Kestrel",
6+
"description": "ASP.NET Core WebSocket echo server using .NET 10 preview with Kestrel and minimal API.",
7+
"repo": "https://github.com/dotnet/aspnetcore",
8+
"enabled": true,
9+
"tests": [
10+
"echo-ws"
11+
]
12+
}

scripts/benchmark.sh

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ CERTS_DIR="$ROOT_DIR/certs"
2424
# Profile definitions: pipeline|req_per_conn|cpu_limit|connections|endpoint
2525
# endpoint: empty = /baseline11 (raw), "json" = /json (GET), "compression" = /compression (GET+gzip), "pipeline" = /pipeline, "upload" = POST /upload (raw),
2626
# "h2" = /baseline2 (h2load), "static-h2" = multi-URI h2load, "h3" = /baseline2 (oha HTTP/3), "static-h3" = multi-URI oha,
27-
# "grpc" = gRPC unary (h2load h2c), "grpc-tls" = gRPC unary (h2load TLS)
27+
# "grpc" = gRPC unary (h2load h2c), "grpc-tls" = gRPC unary (h2load TLS),
28+
# "ws-echo" = WebSocket echo (gcannon --ws)
2829
declare -A PROFILES=(
2930
[baseline]="1|0||512,4096,16384|"
3031
[pipelined]="16|0||512,4096,16384|pipeline"
@@ -40,8 +41,9 @@ declare -A PROFILES=(
4041
[static-h3]="32|0||256,512|static-h3"
4142
[unary-grpc]="1|0||256,1024|grpc"
4243
[unary-grpc-tls]="1|0||256,1024|grpc-tls"
44+
[echo-ws]="16|0||512,4096,16384|ws-echo"
4345
)
44-
PROFILE_ORDER=(baseline pipelined limited-conn json upload compression noisy mixed baseline-h2 static-h2 baseline-h3 static-h3 unary-grpc unary-grpc-tls)
46+
PROFILE_ORDER=(baseline pipelined limited-conn json upload compression noisy mixed baseline-h2 static-h2 baseline-h3 static-h3 unary-grpc unary-grpc-tls echo-ws)
4547

4648
# Parse flags
4749
SAVE_RESULTS=false
@@ -375,6 +377,8 @@ for profile in "${profiles_to_run[@]}"; do
375377
local_check_url="http://localhost:$PORT/baseline11?a=1&b=1"
376378
elif [ "$endpoint" = "json" ]; then
377379
local_check_url="http://localhost:$PORT/json"
380+
elif [ "$endpoint" = "ws-echo" ]; then
381+
local_check_url="http://localhost:$PORT/ws"
378382
else
379383
local_check_url="http://localhost:$PORT/baseline11?a=1&b=1"
380384
fi
@@ -396,7 +400,11 @@ for profile in "${profiles_to_run[@]}"; do
396400
# Build load generator args based on profile endpoint
397401
USE_H2LOAD=false
398402
USE_OHA=false
399-
if [ "$endpoint" = "grpc" ]; then
403+
if [ "$endpoint" = "ws-echo" ]; then
404+
gc_args=("http://localhost:$PORT/ws"
405+
--ws
406+
-c "$CONNS" -t "$THREADS" -d "$DURATION" -p "$pipeline")
407+
elif [ "$endpoint" = "grpc" ]; then
400408
USE_H2LOAD=true
401409
gc_args=("$H2LOAD"
402410
"http://localhost:$PORT/benchmark.BenchmarkService/GetSum"

site/content/docs/add-framework/meta-json.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,6 @@ Create a `meta.json` file in your framework directory:
4848
| `static-h3` | HTTP/3 | `/static/*` (QUIC, port 8443) |
4949
| `unary-grpc` | gRPC | `BenchmarkService/GetSum` (h2c, port 8080) |
5050
| `unary-grpc-tls` | gRPC | `BenchmarkService/GetSum` (TLS, port 8443) |
51+
| `echo-ws` | WebSocket | `/ws` echo (port 8080) |
5152

5253
Only include profiles your framework supports. Frameworks missing a profile simply don't appear in that profile's leaderboard.

site/content/docs/add-framework/test-profiles/_index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,5 @@ Data files are **mounted automatically** by the benchmark runner — your Docker
3535
{{< card link="h2" title="HTTP/2" subtitle="Baseline and static file benchmarks over encrypted TLS connections with stream multiplexing." icon="globe-alt" >}}
3636
{{< card link="h3" title="HTTP/3" subtitle="Baseline and static file benchmarks over QUIC for frameworks with native HTTP/3 support." icon="globe-alt" >}}
3737
{{< card link="grpc" title="gRPC" subtitle="Unary RPC throughput over cleartext HTTP/2 using Protocol Buffers serialization." icon="globe-alt" >}}
38+
{{< card link="ws" title="WebSocket" subtitle="WebSocket echo throughput measuring frame processing performance." icon="globe-alt" >}}
3839
{{< /cards >}}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
title: WebSocket
3+
---
4+
5+
WebSocket test profiles measure framework performance for real-time bidirectional communication. The server listens on **port 8080** and accepts WebSocket upgrade requests.
6+
7+
{{< cards >}}
8+
{{< card link="echo" title="Echo" subtitle="WebSocket echo throughput — upgrade, send messages, receive echoes." icon="globe-alt" >}}
9+
{{< /cards >}}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
---
2+
title: Echo (WebSocket)
3+
---
4+
5+
Measures WebSocket echo throughput. Each connection upgrades via HTTP/1.1, then sends text messages and receives echoes. Each echo counts as one completed response.
6+
7+
**Connections:** 512, 4,096, 16,384
8+
**Pipeline:** 16 (messages in flight per connection)
9+
10+
## Workload
11+
12+
1. Open TCP connection to port 8080
13+
2. Send HTTP/1.1 upgrade request to `/ws`
14+
3. After receiving `101 Switching Protocols`, switch to WebSocket framing
15+
4. Send text frames containing `"hello"`, receive echo frames
16+
5. Measure messages per second
17+
18+
## What it measures
19+
20+
- WebSocket upgrade handshake performance
21+
- WebSocket frame parsing and construction efficiency
22+
- Echo round-trip latency under load
23+
- Connection scalability for real-time workloads
24+
25+
## Expected upgrade request/response
26+
27+
```
28+
GET /ws HTTP/1.1
29+
Host: localhost:8080
30+
Upgrade: websocket
31+
Connection: Upgrade
32+
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
33+
Sec-WebSocket-Version: 13
34+
```
35+
36+
```
37+
HTTP/1.1 101 Switching Protocols
38+
Upgrade: websocket
39+
Connection: Upgrade
40+
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
41+
```
42+
43+
## Parameters
44+
45+
| Parameter | Value |
46+
|-----------|-------|
47+
| Endpoint | `/ws` (WebSocket upgrade) |
48+
| Connections | 512, 4,096, 16,384 |
49+
| Pipeline | 16 (messages in flight) |
50+
| Message | `"hello"` (5 bytes, text frame) |
51+
| Duration | 5s |
52+
| Runs | 3 (best taken) |
53+
| Load generator | gcannon `--ws` |

site/content/leaderboard/_index.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ article { max-width: 100% !important; }
2222
<span class="http-ver" data-ver="h2">HTTP/2</span>
2323
<span class="http-ver" data-ver="h3">HTTP/3</span>
2424
<span class="http-ver" data-ver="grpc">gRPC</span>
25+
<span class="http-ver" data-ver="ws">WebSocket</span>
2526
</div>
2627
</div>
2728
<style>
@@ -53,6 +54,8 @@ html.dark .http-ver[data-ver="h3"].active { color: #4ade80; background: rgba(34,
5354
html.dark .http-ver[data-ver="composite"].active { color: #fb923c; background: rgba(249,115,22,0.15); }
5455
.http-ver[data-ver="grpc"].active { color: #7c3aed; background: rgba(124,58,237,0.12); box-shadow: 0 2px 8px rgba(124,58,237,0.15), 0 1px 3px rgba(0,0,0,0.08); font-weight: 700; }
5556
html.dark .http-ver[data-ver="grpc"].active { color: #a78bfa; background: rgba(124,58,237,0.15); }
57+
.http-ver[data-ver="ws"].active { color: #0891b2; background: rgba(8,145,178,0.12); box-shadow: 0 2px 8px rgba(8,145,178,0.15), 0 1px 3px rgba(0,0,0,0.08); font-weight: 700; }
58+
html.dark .http-ver[data-ver="ws"].active { color: #22d3ee; background: rgba(8,145,178,0.15); }
5659
</style>
5760
<script>
5861
(function() {
@@ -67,6 +70,7 @@ html.dark .http-ver[data-ver="grpc"].active { color: #a78bfa; background: rgba(1
6770
document.getElementById('lb-h3-wrapper').style.display = ver === 'h3' ? '' : 'none';
6871
document.getElementById('lb-composite-wrapper').style.display = ver === 'composite' ? '' : 'none';
6972
document.getElementById('lb-grpc-wrapper').style.display = ver === 'grpc' ? '' : 'none';
73+
document.getElementById('lb-ws-wrapper').style.display = ver === 'ws' ? '' : 'none';
7074
});
7175
});
7276
})();
@@ -89,6 +93,10 @@ html.dark .http-ver[data-ver="grpc"].active { color: #a78bfa; background: rgba(1
8993
{{< leaderboard-grpc >}}
9094
</div>
9195

96+
<div id="lb-ws-wrapper" style="display:none;">
97+
{{< leaderboard-ws >}}
98+
</div>
99+
92100
<div id="lb-composite-wrapper">
93101
{{< leaderboard-composite >}}
94102
</div>

0 commit comments

Comments
 (0)