From 1875176871df28f19df184c1252989be287e64e2 Mon Sep 17 00:00:00 2001 From: Jeff JF Wu Date: Mon, 12 Oct 2026 15:37:45 +0800 Subject: [PATCH 1/5] Add aspnet-minimal-satoriGC --- frameworks/aspnet-minimal-satoriGC/Dockerfile | 21 ++ .../aspnet-minimal-satoriGC/Handlers.cs | 136 +++++++++++ .../Pages/Fortunes.cshtml | 16 ++ .../Pages/Fortunes.cshtml.cs | 19 ++ frameworks/aspnet-minimal-satoriGC/Program.cs | 157 +++++++++++++ frameworks/aspnet-minimal-satoriGC/README.md | 40 ++++ .../RotatingCertificate.cs | 99 ++++++++ .../Services/DatabaseService.cs | 78 +++++++ .../Services/DatasetService.cs | 58 +++++ .../Services/FortuneService.cs | 32 +++ .../Services/ItemService.cs | 214 ++++++++++++++++++ .../Types/AppJsonContext.cs | 19 ++ .../aspnet-minimal-satoriGC/Types/Types.cs | 68 ++++++ .../aspnet-minimal-satoriGC.csproj | 12 + frameworks/aspnet-minimal-satoriGC/build.sh | 5 + frameworks/aspnet-minimal-satoriGC/meta.json | 39 ++++ 16 files changed, 1013 insertions(+) create mode 100644 frameworks/aspnet-minimal-satoriGC/Dockerfile create mode 100644 frameworks/aspnet-minimal-satoriGC/Handlers.cs create mode 100644 frameworks/aspnet-minimal-satoriGC/Pages/Fortunes.cshtml create mode 100644 frameworks/aspnet-minimal-satoriGC/Pages/Fortunes.cshtml.cs create mode 100644 frameworks/aspnet-minimal-satoriGC/Program.cs create mode 100644 frameworks/aspnet-minimal-satoriGC/README.md create mode 100644 frameworks/aspnet-minimal-satoriGC/RotatingCertificate.cs create mode 100644 frameworks/aspnet-minimal-satoriGC/Services/DatabaseService.cs create mode 100644 frameworks/aspnet-minimal-satoriGC/Services/DatasetService.cs create mode 100644 frameworks/aspnet-minimal-satoriGC/Services/FortuneService.cs create mode 100644 frameworks/aspnet-minimal-satoriGC/Services/ItemService.cs create mode 100644 frameworks/aspnet-minimal-satoriGC/Types/AppJsonContext.cs create mode 100644 frameworks/aspnet-minimal-satoriGC/Types/Types.cs create mode 100644 frameworks/aspnet-minimal-satoriGC/aspnet-minimal-satoriGC.csproj create mode 100644 frameworks/aspnet-minimal-satoriGC/build.sh create mode 100644 frameworks/aspnet-minimal-satoriGC/meta.json diff --git a/frameworks/aspnet-minimal-satoriGC/Dockerfile b/frameworks/aspnet-minimal-satoriGC/Dockerfile new file mode 100644 index 000000000..e8ccd4c19 --- /dev/null +++ b/frameworks/aspnet-minimal-satoriGC/Dockerfile @@ -0,0 +1,21 @@ +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build +WORKDIR /app +COPY frameworks/aspnet-minimal-satoriGC/ . +RUN dotnet publish -c Release -r linux-x64 --self-contained -o out + +FROM mcr.microsoft.com/dotnet/aspnet:10.0 + + +ADD https://packages.microsoft.com/config/debian/12/packages-microsoft-prod.deb /packages-microsoft-prod.deb + +RUN dpkg -i packages-microsoft-prod.deb && rm packages-microsoft-prod.deb \ + && apt-get update \ + && apt-get install -y --no-install-recommends libmsquic \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app +COPY --from=build /app/out . +EXPOSE 8080 8081 8082 8443/tcp 8443/udp + +ENTRYPOINT ["dotnet", "aspnet-minimal-satoriGC.dll"] \ No newline at end of file diff --git a/frameworks/aspnet-minimal-satoriGC/Handlers.cs b/frameworks/aspnet-minimal-satoriGC/Handlers.cs new file mode 100644 index 000000000..36dc7ccd0 --- /dev/null +++ b/frameworks/aspnet-minimal-satoriGC/Handlers.cs @@ -0,0 +1,136 @@ + +using HttpArena.Services; +using HttpArena.Types; +using System.Buffers; + +static class Handlers +{ + + public static string Sum(int a, int b) => (a + b).ToString(); + + public static async ValueTask SumBody(int a, int b, HttpRequest req) + { + using var reader = new StreamReader(req.Body); + return (a + b + int.Parse(await reader.ReadToEndAsync())).ToString(); + } + + public static string Text() => "ok"; + + // GET /delay/{ms} - answer after ms milliseconds. Task.Delay registers a + // timer and yields; the thread goes back to the pool instead of sitting on + // the request, so the number of waits in flight is bounded by memory rather + // than by the pool size. + public static async ValueTask Delay(int ms) + { + if (ms > 0) await Task.Delay(ms); + return ms.ToString(); + } + + // Echo: the bytes that arrived go back unchanged. + // + // When the request declares its length - which it does on this profile - the + // body is read once into a pooled buffer of exactly that size and written + // once. The obvious `new MemoryStream()` costs twice over: it doubles as it + // grows, so reaching 100 KB takes ~10 allocations and a full recopy, and its + // final buffer lands above the 85,000-byte Large Object Heap threshold, which + // is collected as gen2 and not compacted. ArrayPool hands back the same + // buffer instead of allocating one per request. Measured at -12% CPU on the + // 8Gbit profile, with a tighter p99. + // + // A chunked request carries no length to size against, so it keeps the + // collect-then-write path - which is also what makes that case correct. + public static async Task EchoBody(HttpContext ctx) + { + ctx.Response.ContentType = "application/octet-stream"; + + if (ctx.Request.ContentLength is long cl && cl > 0) + { + int len = (int)cl; + byte[] buf = ArrayPool.Shared.Rent(len); + try + { + await ctx.Request.Body.ReadExactlyAsync(buf.AsMemory(0, len)); + ctx.Response.ContentLength = len; + await ctx.Response.Body.WriteAsync(buf.AsMemory(0, len)); + } + finally + { + ArrayPool.Shared.Return(buf); + } + return; + } + + using var ms = new MemoryStream(); + await ctx.Request.Body.CopyToAsync(ms); + var body = ms.GetBuffer().AsMemory(0, (int)ms.Length); + ctx.Response.ContentLength = body.Length; + await ctx.Response.Body.WriteAsync(body); + } + + public static IResult Json(int count, DatasetService dataset, int m = 1) + { + var response = dataset.GetItems(count, m); + + if (response is null) + return TypedResults.Problem("Dataset not loaded"); + + return TypedResults.Ok(response); + } + + public static async Task AsyncDatabase(ItemService items, double min = 10, double max = 50, int limit = 50) + { + if (!items.IsAvailable) + return TypedResults.Problem("DB not available"); + + var response = await items.QueryAsync(min, max, limit); + + return TypedResults.Ok(response); + } + + public static async Task CrudList(ItemService items, string? category = null, int page = 0, int limit = 0) + { + if (!items.IsAvailable) + return TypedResults.Problem("DB not available"); + + var response = await items.ListAsync(category, page, limit); + + return TypedResults.Ok(response); + } + + public static async Task CrudRead(int id, ItemService items, HttpContext ctx) + { + if (!items.IsAvailable) + return TypedResults.Problem("DB not available"); + + var result = await items.ReadAsync(id); + if (result is null) return TypedResults.NotFound(); + + ctx.Response.Headers["X-Cache"] = result.CacheHit ? "HIT" : "MISS"; + + return result.Json is not null + ? Results.Content(result.Json, "application/json") + : TypedResults.Ok(result.Item!); + } + + public static async Task CrudCreate(CrudItemInput input, ItemService items) + { + if (!items.IsAvailable) + return TypedResults.Problem("DB not available"); + + var created = await items.CreateAsync(input); + + return TypedResults.Created((string?)null, created); + } + + public static async Task CrudUpdate(int id, CrudItemInput input, ItemService items) + { + if (!items.IsAvailable) + return TypedResults.Problem("DB not available"); + + var updated = await items.UpdateAsync(id, input); + if (updated is null) return TypedResults.NotFound(); + + return TypedResults.Ok(updated); + } + +} diff --git a/frameworks/aspnet-minimal-satoriGC/Pages/Fortunes.cshtml b/frameworks/aspnet-minimal-satoriGC/Pages/Fortunes.cshtml new file mode 100644 index 000000000..880992619 --- /dev/null +++ b/frameworks/aspnet-minimal-satoriGC/Pages/Fortunes.cshtml @@ -0,0 +1,16 @@ +@page "/fortunes" +@model FortunesModel +@{ Layout = null; } + + +Fortunes + + + +@foreach (var f in Model.Fortunes) +{ + +} +
idmessage
@f.Id@f.Message
+ + diff --git a/frameworks/aspnet-minimal-satoriGC/Pages/Fortunes.cshtml.cs b/frameworks/aspnet-minimal-satoriGC/Pages/Fortunes.cshtml.cs new file mode 100644 index 000000000..5e55ecde1 --- /dev/null +++ b/frameworks/aspnet-minimal-satoriGC/Pages/Fortunes.cshtml.cs @@ -0,0 +1,19 @@ +using HttpArena.Services; +using HttpArena.Types; + +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; + +public sealed class FortunesModel(FortuneService fortuneService) : PageModel +{ + public List Fortunes { get; private set; } = []; + + public async Task OnGetAsync() + { + if (!fortuneService.IsAvailable) + return new StatusCodeResult(500); + + Fortunes = await fortuneService.GetFortunesAsync(); + return Page(); + } +} diff --git a/frameworks/aspnet-minimal-satoriGC/Program.cs b/frameworks/aspnet-minimal-satoriGC/Program.cs new file mode 100644 index 000000000..2774947ec --- /dev/null +++ b/frameworks/aspnet-minimal-satoriGC/Program.cs @@ -0,0 +1,157 @@ +using HttpArena; +using HttpArena.Services; +using HttpArena.Types; +using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.AspNetCore.StaticFiles; +using Microsoft.Extensions.FileProviders; +using System.Security.Cryptography.X509Certificates; + +var builder = WebApplication.CreateBuilder(args); +builder.Logging.ClearProviders(); +builder.Services.AddRazorPages(); + +builder.Services.ConfigureHttpJsonOptions(o => + o.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonContext.Default)); + +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); + +var certPath = Environment.GetEnvironmentVariable("TLS_CERT") ?? "/certs/server.crt"; +var keyPath = Environment.GetEnvironmentVariable("TLS_KEY") ?? "/certs/server.key"; +var hasCert = File.Exists(certPath) && File.Exists(keyPath); + +// The opt-in tls_check gets its own listener on :9000 and its own pair at +// /certs-tls. It rotates certificates under a running server, and pointing it +// at /certs would move the ground under json-tls, static-tls and the h2 +// profiles in the same validation run. +var tlsCheckCert = "/certs-tls/server.crt"; +var tlsCheckKey = "/certs-tls/server.key"; +var hasTlsCheck = File.Exists(tlsCheckCert) && File.Exists(tlsCheckKey); + +builder.WebHost.ConfigureKestrel(options => +{ + options.Limits.Http2.MaxStreamsPerConnection = 256; + options.Limits.Http2.InitialConnectionWindowSize = 2 * 1024 * 1024; + options.Limits.Http2.InitialStreamWindowSize = 1024 * 1024; + + options.ListenAnyIP(8080, lo => + { + lo.Protocols = HttpProtocols.Http1; + }); + + // h2c prior-knowledge listener for the baseline-h2c / json-h2c profiles. + // Protocols = Http2 with no UseHttps() gives Kestrel cleartext HTTP/2 + // from the first byte. Clients that try HTTP/1.1 on this port get + // rejected, which is what validate.sh's h2c anti-cheat requires. + options.ListenAnyIP(8082, lo => + { + lo.Protocols = HttpProtocols.Http2; + }); + + if (hasCert) + { + // Re-read when the files change, so a rotation lands without a restart. + // The selector below runs per handshake, which is what makes that + // visible to the next connection rather than the next process. + var cert = new RotatingCertificate(certPath, keyPath); + + options.ListenAnyIP(8443, lo => + { + lo.Protocols = HttpProtocols.Http1AndHttp2AndHttp3; + lo.UseHttps(https => https.ServerCertificateSelector = (_, _) => cert.Current); + }); + + // HTTP/1.1-only TLS listener for the json-tls profile. Kestrel + // advertises http/1.1 via ALPN so HTTP/1.1-only clients (wrk) negotiate + // correctly and never upgrade to h2. + options.ListenAnyIP(8081, lo => + { + lo.Protocols = HttpProtocols.Http1; + lo.UseHttps(https => https.ServerCertificateSelector = (_, _) => cert.Current); + }); + } + + if (hasTlsCheck) + { + // Same rotating handle, a different pair. The selector runs per + // handshake, which is what lets a replaced file reach the next + // connection instead of the next process. + var checkCert = new RotatingCertificate(tlsCheckCert, tlsCheckKey); + + options.ListenAnyIP(9000, lo => + { + lo.Protocols = HttpProtocols.Http1; + lo.UseHttps(https => https.ServerCertificateSelector = (_, _) => checkCert.Current); + }); + } +}); + +builder.Services.AddResponseCompression(); + +var app = builder.Build(); + +app.UseResponseCompression(); + +app.Use((ctx, next) => +{ + ctx.Response.Headers.Server = "aspnet-minimal"; + return next(); +}); + +// Load the dataset and open the Postgres/Redis connections at startup +// instead of on the first request. +_ = app.Services.GetRequiredService(); +_ = app.Services.GetRequiredService(); +_ = app.Services.GetRequiredService(); + +app.MapGet("/pipeline", Handlers.Text); + +app.MapGet("/baseline11", Handlers.Sum); +app.MapPost("/baseline11", Handlers.SumBody); +app.MapGet("/baseline2", Handlers.Sum); + +app.MapPost("/echo", Handlers.EchoBody); +app.MapGet("/delay/{ms:int}", Handlers.Delay); +app.MapGet("/json/{count}", Handlers.Json); +app.MapGet("/async-db", Handlers.AsyncDatabase); + +// ── CRUD endpoints ───────────────────────────────────────────────────────── +// Realistic REST API: paginated list, cached single-item read, create, update. +// Cache-aside single-item reads (Redis or in-process), invalidated on PUT. + +app.MapGet("/crud/items", Handlers.CrudList); +app.MapGet("/crud/items/{id:int}", Handlers.CrudRead); +app.MapPost("/crud/items", Handlers.CrudCreate); +app.MapPut("/crud/items/{id:int}", Handlers.CrudUpdate); + +// /fortunes is served by the Razor page at Pages/Fortunes.cshtml +// (route "/fortunes" declared via the page's @page directive). MapRazorPages +// wires up the MVC/Razor pipeline so the page model can render Razor markup +// — the standard ASP.NET production path for HTML responses. +app.MapRazorPages(); + +// Served straight out of the directory the profile mounts, rather than a copy +// taken at image build. MapStaticAssets, which this used before, resolves assets +// through a manifest generated at compile time from wwwroot: the container ended +// up holding two copies of the corpus and answering from the one the harness +// cannot touch, so a file replaced on disk was never reflected in a response. +// +// UseStaticFiles reads the file per request through the file provider, so what is +// served follows the mounted directory. Compression is still ASP.NET's own +// response compression middleware, configured above. +var staticContentTypes = new FileExtensionContentTypeProvider(); +staticContentTypes.Mappings[".webp"] = "image/webp"; +staticContentTypes.Mappings[".woff2"] = "font/woff2"; + +app.UseStaticFiles(new StaticFileOptions +{ + FileProvider = new PhysicalFileProvider("/data/static"), + RequestPath = "/static", + ContentTypeProvider = staticContentTypes, + ServeUnknownFileTypes = false +}); + + +app.Run(); diff --git a/frameworks/aspnet-minimal-satoriGC/README.md b/frameworks/aspnet-minimal-satoriGC/README.md new file mode 100644 index 000000000..c916a9d8b --- /dev/null +++ b/frameworks/aspnet-minimal-satoriGC/README.md @@ -0,0 +1,40 @@ +# aspnet-minimal + +Minimal ASP.NET Core HTTP server using .NET 10 with Kestrel, minimal API routing and Satori GC. + +## Stack + +- **Language:** C# / .NET 10 +- **Framework:** ASP.NET Core Minimal APIs +- **Engine:** Kestrel +- **Build:** Framework-dependent publish, `mcr.microsoft.com/dotnet/aspnet:10.0` runtime (Debian 12) with `libmsquic` installed for HTTP/3 + +## Endpoints + +| Endpoint | Method | Description | +| ------------------ | ------ | ----------------------------------------------------------------------------------------------------------------------- | +| `/pipeline` | GET | Returns `ok` (plain text) | +| `/baseline11` | GET | Sums query parameter values | +| `/baseline11` | POST | Sums query parameters + request body | +| `/baseline2` | GET | Sums query parameter values (HTTP/2 variant) | +| `/json/{count}` | GET | Returns `count` items from the preloaded dataset; honors `Accept-Encoding: gzip/br/deflate` for the `json-comp` profile | +| `/async-db` | GET | Postgres range query: `SELECT ... WHERE price BETWEEN $min AND $max LIMIT $limit` | +| `/echo` | POST | Returns the request body back verbatim | +| `/crud/items` | GET | Paginated list by category with two queries (data + count) | +| `/crud/items/{id}` | GET | Single item read with `IMemoryCache` (1s TTL), returns `X-Cache: HIT/MISS` | +| `/crud/items` | POST | Create item via INSERT with ON CONFLICT upsert, returns 201 | +| `/crud/items/{id}` | PUT | Update item and invalidate cache entry | +| `/static/*` | GET | Serves files from `/data/static` via `MapStaticAssets` with precomputed ETags + compression | + +## Notes + +- HTTP/1.1 on port 8080, HTTP/1+2+3 on port 8443 (TCP **and** UDP for QUIC), h1+TLS on port 8081 (`json-tls` profile) +- HTTP/3 via MsQuic (`libmsquic` installed in the runtime image); Kestrel advertises h3 through the default Alt-Svc header so clients upgrade from h2 +- TLS certs loaded from `$TLS_CERT` / `$TLS_KEY` (default `/certs/server.crt` + `/certs/server.key`) +- Logging disabled (`ClearProviders()`) for throughput; `Server: aspnet-minimal` header set via a lightweight middleware +- `AddResponseCompression()` + `UseResponseCompression()` drives `/json/{count}` gzip encoding for the `json-comp` profile +- HTTP/2 tuned: 256 max streams per connection, 2 MB initial connection window, 1 MB stream window +- `/echo` reads the request body into a 64 KB pooled buffer (`ArrayPool.Shared`) and returns those bytes back unchanged — no full-body allocation +- JSON responses use source-generated `JsonSerializerContext` (`AppJsonContext`) so the hot path avoids reflection +- Postgres pooled via `Npgsql.NpgsqlDataSource` built once at startup from `DATABASE_URL` +- Source split: `Program.cs` (startup + Kestrel + routes), `Handlers.cs` (HTTP adapters), `Services/` (framework-agnostic application logic: dataset, Postgres/Redis connections, item queries, fortunes), `Types/` (DTOs + JSON serializer context). `Services/` and `Types/` have no ASP.NET dependencies and can be copied as-is into other framework integrations. diff --git a/frameworks/aspnet-minimal-satoriGC/RotatingCertificate.cs b/frameworks/aspnet-minimal-satoriGC/RotatingCertificate.cs new file mode 100644 index 000000000..ab1d17baa --- /dev/null +++ b/frameworks/aspnet-minimal-satoriGC/RotatingCertificate.cs @@ -0,0 +1,99 @@ +using System.Security.Cryptography.X509Certificates; + +namespace HttpArena; + +/// +/// The server certificate, re-read from disk when the file underneath it +/// changes. +/// +/// Kestrel's ServerCertificateSelector runs per handshake, so this is where a +/// rotation becomes visible without a restart: the selector asks for Current, +/// and Current notices the PEM was replaced. That is what the opt-in `tls` +/// profile checks -- a certificate is renewed roughly every 60 days in +/// production, and a server that needs a restart to pick one up is a weaker +/// server. +/// +/// The mtime check is throttled rather than run on every handshake. A stat is +/// cheap next to a TLS handshake, but not next to a resumed one, and a second +/// of staleness costs nothing when the thing being tracked changes every two +/// months. +/// +internal sealed class RotatingCertificate : IDisposable +{ + private const int CheckIntervalMs = 1000; + + private readonly string _certPath; + private readonly string _keyPath; + private readonly object _gate = new(); + + private X509Certificate2 _current; + private DateTime _loadedStamp; + private long _lastCheck; + + public RotatingCertificate(string certPath, string keyPath) + { + _certPath = certPath; + _keyPath = keyPath; + _current = Load(certPath, keyPath); + _loadedStamp = Stamp(certPath, keyPath); + _lastCheck = Environment.TickCount64; + } + + public X509Certificate2 Current + { + get + { + var now = Environment.TickCount64; + if (now - Interlocked.Read(ref _lastCheck) >= CheckIntervalMs) + { + Interlocked.Exchange(ref _lastCheck, now); + ReloadIfChanged(); + } + return Volatile.Read(ref _current); + } + } + + private void ReloadIfChanged() + { + try + { + var stamp = Stamp(_certPath, _keyPath); + if (stamp == _loadedStamp) return; + + lock (_gate) + { + if (stamp == _loadedStamp) return; + // A rotation is two files. Loading between the two writes gives + // a mismatched pair, so a failure here is left for the next + // check rather than thrown at a handshake in progress. + var fresh = Load(_certPath, _keyPath); + var previous = _current; + Volatile.Write(ref _current, fresh); + _loadedStamp = stamp; + previous.Dispose(); + } + } + catch + { + // Keep serving the certificate that works. + } + } + + private static DateTime Stamp(string certPath, string keyPath) + { + var c = File.GetLastWriteTimeUtc(certPath); + var k = File.GetLastWriteTimeUtc(keyPath); + return c > k ? c : k; + } + + private static X509Certificate2 Load(string certPath, string keyPath) + { + // CreateFromPemFile hands back a certificate whose key is ephemeral, + // which SslStream will not use on every platform; the PKCS12 round trip + // gives it one it will. + using var pem = X509Certificate2.CreateFromPemFile(certPath, keyPath); + return X509CertificateLoader.LoadPkcs12(pem.Export(X509ContentType.Pkcs12), null); + } + + public void Dispose() => _current.Dispose(); +} diff --git a/frameworks/aspnet-minimal-satoriGC/Services/DatabaseService.cs b/frameworks/aspnet-minimal-satoriGC/Services/DatabaseService.cs new file mode 100644 index 000000000..b0f550356 --- /dev/null +++ b/frameworks/aspnet-minimal-satoriGC/Services/DatabaseService.cs @@ -0,0 +1,78 @@ +using Npgsql; + +using StackExchange.Redis; + +namespace HttpArena.Services; + +/// +/// Owns the shared Postgres connection pool and the optional Redis +/// connection, both configured from environment variables. Either may be +/// unavailable; consumers must check for null. +/// +public sealed class DatabaseService +{ + public NpgsqlDataSource? Postgres { get; } + + // Optional Redis cache for the crud profile. When REDIS_URL is set, + // ItemService uses Redis as a shared cache; otherwise it uses an + // in-process MemoryCache. Mirrors hono-bun's pattern so frameworks + // can be compared apples-to-apples on the same cache topology. + public IDatabase? Redis { get; } + + public DatabaseService() + { + Postgres = OpenPostgres(); + Redis = OpenRedis(); + } + + private static NpgsqlDataSource? OpenPostgres() + { + var dbUrl = Environment.GetEnvironmentVariable("DATABASE_URL"); + if (string.IsNullOrEmpty(dbUrl)) return null; + try + { + var uri = new Uri(dbUrl); + var userInfo = uri.UserInfo.Split(':', 2); + var maxConn = int.TryParse(Environment.GetEnvironmentVariable("DATABASE_MAX_CONN"), out var p) && p > 0 ? p : 256; + + var connStr = new NpgsqlConnectionStringBuilder + { + Host = uri.Host, + Username = Uri.UnescapeDataString(userInfo[0]), + Password = userInfo.Length > 1 ? Uri.UnescapeDataString(userInfo[1]) : null, + Database = uri.AbsolutePath.TrimStart('/'), + MaxPoolSize = maxConn, + MinPoolSize = Math.Min(64, maxConn), + Multiplexing = true, + MaxAutoPrepare = 20 + }; + + if (uri.Port > 0) connStr.Port = uri.Port; + + return new NpgsqlDataSourceBuilder(connStr.ConnectionString).Build(); + } + catch + { + return null; + } + } + + private static IDatabase? OpenRedis() + { + var redisUrl = Environment.GetEnvironmentVariable("REDIS_URL"); + if (string.IsNullOrEmpty(redisUrl)) return null; + try + { + // REDIS_URL is "redis://host:port" — convert to StackExchange's + // "host:port" configuration string. + var uri = new Uri(redisUrl); + var config = ConfigurationOptions.Parse($"{uri.Host}:{uri.Port}"); + config.AbortOnConnectFail = false; + return ConnectionMultiplexer.Connect(config).GetDatabase(); + } + catch + { + return null; + } + } +} diff --git a/frameworks/aspnet-minimal-satoriGC/Services/DatasetService.cs b/frameworks/aspnet-minimal-satoriGC/Services/DatasetService.cs new file mode 100644 index 000000000..151b09283 --- /dev/null +++ b/frameworks/aspnet-minimal-satoriGC/Services/DatasetService.cs @@ -0,0 +1,58 @@ +using System.Text.Json; + +using HttpArena.Types; + +namespace HttpArena.Services; + +/// +/// Serves the preloaded JSON dataset for the /json workload. +/// +public sealed class DatasetService +{ + private readonly List? _items; + + public DatasetService() + { + var path = Environment.GetEnvironmentVariable("DATASET_PATH") ?? "/data/dataset.json"; + if (File.Exists(path)) + { + _items = JsonSerializer.Deserialize(File.ReadAllText(path), AppJsonContext.Default.ListItem); + } + } + + public bool IsAvailable => _items is not null; + + /// + /// Returns the first dataset items with their + /// total computed as price * quantity * , + /// or null when no dataset is loaded. + /// + public ItemsResponse? GetItems(int count, int multiplier) + { + if (_items is null) return null; + + if (count > _items.Count) count = _items.Count; + if (count < 0) count = 0; + + var items = new ProcessedItem[count]; + + for (int i = 0; i < count; i++) + { + var item = _items[i]; + items[i] = new ProcessedItem + { + Id = item.Id, + Name = item.Name, + Category = item.Category, + Price = item.Price, + Quantity = item.Quantity, + Active = item.Active, + Tags = item.Tags, + Rating = item.Rating, + Total = item.Price * item.Quantity * multiplier + }; + } + + return new ItemsResponse(items, count); + } +} diff --git a/frameworks/aspnet-minimal-satoriGC/Services/FortuneService.cs b/frameworks/aspnet-minimal-satoriGC/Services/FortuneService.cs new file mode 100644 index 000000000..dfec3f90f --- /dev/null +++ b/frameworks/aspnet-minimal-satoriGC/Services/FortuneService.cs @@ -0,0 +1,32 @@ +using HttpArena.Types; + +namespace HttpArena.Services; + +/// +/// Loads and prepares the fortune rows for the /fortunes HTML workload. +/// +public sealed class FortuneService(DatabaseService database) +{ + public bool IsAvailable => database.Postgres is not null; + + public async Task> GetFortunesAsync() + { + var list = new List(201); + + await using (var cmd = database.Postgres!.CreateCommand("SELECT id, message FROM fortune")) + await using (var reader = await cmd.ExecuteReaderAsync()) + { + while (await reader.ReadAsync()) + { + list.Add(new Fortune(reader.GetInt32(0), reader.GetString(1))); + } + } + + // Runtime-injected row defeats whole-page memoization: the rendered + // HTML must vary per request, even though the seeded rows don't. + list.Add(new Fortune(0, "Additional fortune added at request time.")); + list.Sort(static (a, b) => string.CompareOrdinal(a.Message, b.Message)); + + return list; + } +} diff --git a/frameworks/aspnet-minimal-satoriGC/Services/ItemService.cs b/frameworks/aspnet-minimal-satoriGC/Services/ItemService.cs new file mode 100644 index 000000000..831d8a0fe --- /dev/null +++ b/frameworks/aspnet-minimal-satoriGC/Services/ItemService.cs @@ -0,0 +1,214 @@ +using System.Text.Json; + +using HttpArena.Types; + +using Microsoft.Extensions.Caching.Memory; + +using Npgsql; + +namespace HttpArena.Services; + +/// +/// Item queries against Postgres: the /async-db range query plus a +/// realistic CRUD API with paginated list, cached single-item read, +/// create, and update. Cache-aside on single-item reads with 200ms TTL, +/// invalidated on update. List queries always hit Postgres. +/// +public sealed class ItemService +{ + private static readonly TimeSpan CacheTtl = TimeSpan.FromMilliseconds(200); + + private static readonly MemoryCacheEntryOptions CacheEntryOptions = + new() { AbsoluteExpirationRelativeToNow = CacheTtl }; + + private readonly DatabaseService _database; + + // In-process fallback cache; only constructed when Redis is not configured. + private readonly MemoryCache? _cache; + + public ItemService(DatabaseService database) + { + _database = database; + + if (database.Redis is null) + { + _cache = new MemoryCache(new MemoryCacheOptions()); + } + } + + public bool IsAvailable => _database.Postgres is not null; + + /// + /// Range query for /async-db: items with price between + /// and . + /// + public async Task> QueryAsync(double min, double max, int limit) + { + limit = Math.Clamp(limit, 1, 50); + + await using var cmd = _database.Postgres!.CreateCommand( + "SELECT id, name, category, price, quantity, active, tags, rating_score, rating_count FROM items WHERE price BETWEEN $1 AND $2 LIMIT $3"); + + cmd.Parameters.AddWithValue(min); + cmd.Parameters.AddWithValue(max); + cmd.Parameters.AddWithValue(limit); + + await using var reader = await cmd.ExecuteReaderAsync(); + + var items = new List(limit); + + while (await reader.ReadAsync()) + { + items.Add(ReadItem(reader)); + } + + return new ItemsResponse(items, items.Count); + } + + /// + /// Paginated list by category (always DB, never cached). Out-of-range + /// paging inputs fall back to page 1 / limit 10. + /// + public async Task ListAsync(string? category, int page, int limit) + { + if (string.IsNullOrEmpty(category)) category = "electronics"; + if (page < 1) page = 1; + if (limit < 1 || limit > 50) limit = 10; + var offset = (page - 1) * limit; + + // Single data query. A COUNT(*) pass was 90%+ of PG CPU because + // concurrent writes kept the visibility map dirty, forcing heap + // fetches on every index-only scan. "Load more" pagination + // (return page size, no total) is a realistic alternative that + // removes that dominant cost. + await using var cmd = _database.Postgres!.CreateCommand( + "SELECT id, name, category, price, quantity, active, tags, rating_score, rating_count " + + "FROM items WHERE category = $1 ORDER BY id LIMIT $2 OFFSET $3"); + cmd.Parameters.AddWithValue(category); + cmd.Parameters.AddWithValue(limit); + cmd.Parameters.AddWithValue(offset); + + await using var reader = await cmd.ExecuteReaderAsync(); + var items = new List(); + while (await reader.ReadAsync()) + { + items.Add(ReadItem(reader)); + } + + return new CrudListResponse { Items = items, Total = items.Count, Page = page, Limit = limit }; + } + + /// + /// Single item read, cached with 200ms TTL. Redis when available (the + /// cache stores pre-serialized JSON so the HIT path skips a + /// Serialize+Deserialize round trip); else in-process MemoryCache + /// (caches the typed DTO). Returns null when the item does not exist. + /// + public async Task ReadAsync(int id) + { + var cacheKey = CacheKey(id); + + if (_database.Redis is not null) + { + var cachedJson = await _database.Redis.StringGetAsync(cacheKey); + if (cachedJson.HasValue) + { + return new CachedItemResult(null, (string)cachedJson!, CacheHit: true); + } + + var item = await FetchItemByIdAsync(id); + if (item is null) return null; + + var json = JsonSerializer.Serialize(item, AppJsonContext.Default.Item); + await _database.Redis.StringSetAsync(cacheKey, json, CacheTtl); + return new CachedItemResult(null, json, CacheHit: false); + } + + if (_cache!.TryGetValue(cacheKey, out Item? cached)) + { + return new CachedItemResult(cached, null, CacheHit: true); + } + + var dto = await FetchItemByIdAsync(id); + if (dto is null) return null; + + _cache.Set(cacheKey, dto, CacheEntryOptions); + return new CachedItemResult(dto, null, CacheHit: false); + } + + /// + /// Creates an item (upsert on id conflict). + /// + public async Task CreateAsync(CrudItemInput input) + { + await using var cmd = _database.Postgres!.CreateCommand( + "INSERT INTO items (id, name, category, price, quantity, active, tags, rating_score, rating_count) " + + "VALUES ($1, $2, $3, $4, $5, true, '[\"bench\"]', 0, 0) " + + "ON CONFLICT (id) DO UPDATE SET name = $2, price = $4, quantity = $5 " + + "RETURNING id"); + cmd.Parameters.AddWithValue(input.Id); + cmd.Parameters.AddWithValue(input.Name ?? "New Product"); + cmd.Parameters.AddWithValue(input.Category ?? "test"); + cmd.Parameters.AddWithValue(input.Price); + cmd.Parameters.AddWithValue(input.Quantity); + + var newId = (int)(await cmd.ExecuteScalarAsync())!; + return new CrudWriteResponse { Id = newId, Name = input.Name, Category = input.Category, Price = input.Price, Quantity = input.Quantity }; + } + + /// + /// Updates an item and invalidates its cache entry. Returns null when + /// the item does not exist. + /// + public async Task UpdateAsync(int id, CrudItemInput input) + { + await using var cmd = _database.Postgres!.CreateCommand( + "UPDATE items SET name = $1, price = $2, quantity = $3 WHERE id = $4"); + cmd.Parameters.AddWithValue(input.Name ?? "Updated"); + cmd.Parameters.AddWithValue(input.Price); + cmd.Parameters.AddWithValue(input.Quantity); + cmd.Parameters.AddWithValue(id); + + var affected = await cmd.ExecuteNonQueryAsync(); + if (affected == 0) return null; + + var cacheKey = CacheKey(id); + if (_database.Redis is not null) + { + await _database.Redis.KeyDeleteAsync(cacheKey); + } + else + { + _cache!.Remove(cacheKey); + } + + return new CrudWriteResponse { Id = id, Name = input.Name, Price = input.Price, Quantity = input.Quantity }; + } + + private async Task FetchItemByIdAsync(int id) + { + await using var cmd = _database.Postgres!.CreateCommand( + "SELECT id, name, category, price, quantity, active, tags, rating_score, rating_count " + + "FROM items WHERE id = $1 LIMIT 1"); + cmd.Parameters.AddWithValue(id); + + await using var reader = await cmd.ExecuteReaderAsync(); + if (!await reader.ReadAsync()) return null; + + return ReadItem(reader); + } + + private static Item ReadItem(NpgsqlDataReader reader) => new() + { + Id = reader.GetInt32(0), + Name = reader.GetString(1), + Category = reader.GetString(2), + Price = reader.GetInt32(3), + Quantity = reader.GetInt32(4), + Active = reader.GetBoolean(5), + Tags = JsonSerializer.Deserialize(reader.GetString(6), AppJsonContext.Default.ListString)!, + Rating = new RatingInfo { Score = reader.GetInt32(7), Count = reader.GetInt32(8) } + }; + + private static string CacheKey(int id) => $"crud:{id}"; +} diff --git a/frameworks/aspnet-minimal-satoriGC/Types/AppJsonContext.cs b/frameworks/aspnet-minimal-satoriGC/Types/AppJsonContext.cs new file mode 100644 index 000000000..0c77290b4 --- /dev/null +++ b/frameworks/aspnet-minimal-satoriGC/Types/AppJsonContext.cs @@ -0,0 +1,19 @@ +using System.Text.Json.Serialization; + +namespace HttpArena.Types; + +[JsonSerializable(typeof(ItemsResponse))] +[JsonSerializable(typeof(ItemsResponse))] +[JsonSerializable(typeof(Item))] +[JsonSerializable(typeof(List))] +[JsonSerializable(typeof(ProcessedItem))] +[JsonSerializable(typeof(RatingInfo))] +[JsonSerializable(typeof(List))] +[JsonSerializable(typeof(CrudItemInput))] +[JsonSerializable(typeof(CrudListResponse))] +[JsonSerializable(typeof(CrudWriteResponse))] +[JsonSourceGenerationOptions( + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, + PropertyNameCaseInsensitive = true, + NumberHandling = JsonNumberHandling.AllowReadingFromString)] +public partial class AppJsonContext : JsonSerializerContext { } diff --git a/frameworks/aspnet-minimal-satoriGC/Types/Types.cs b/frameworks/aspnet-minimal-satoriGC/Types/Types.cs new file mode 100644 index 000000000..c25ece37a --- /dev/null +++ b/frameworks/aspnet-minimal-satoriGC/Types/Types.cs @@ -0,0 +1,68 @@ +namespace HttpArena.Types; + +/// +/// An item as stored in the dataset file and the Postgres `items` table. +/// +public sealed class Item +{ + public int Id { get; set; } + public string Name { get; set; } = ""; + public string Category { get; set; } = ""; + public int Price { get; set; } + public int Quantity { get; set; } + public bool Active { get; set; } + public List Tags { get; set; } = []; + public RatingInfo Rating { get; set; } = new(); +} + +/// +/// A dataset item enriched with a computed total for the /json workload. +/// +public sealed class ProcessedItem +{ + public int Id { get; set; } + public string Name { get; set; } = ""; + public string Category { get; set; } = ""; + public int Price { get; set; } + public int Quantity { get; set; } + public bool Active { get; set; } + public List Tags { get; set; } = []; + public RatingInfo Rating { get; set; } = new(); + public long Total { get; set; } +} + +public sealed class RatingInfo +{ + public int Score { get; set; } + public int Count { get; set; } +} + +public sealed record ItemsResponse(IReadOnlyList Items, int Count); + +public sealed record CrudItemInput(int Id, string? Name, string? Category, int Price, int Quantity); + +public sealed class CrudListResponse +{ + public List Items { get; set; } = []; + public long Total { get; set; } + public int Page { get; set; } + public int Limit { get; set; } +} + +public sealed class CrudWriteResponse +{ + public int Id { get; set; } + public string? Name { get; set; } + public string? Category { get; set; } + public int Price { get; set; } + public int Quantity { get; set; } +} + +/// +/// Result of a cached single-item read. Exactly one of +/// (typed, from the in-process cache path) or +/// (pre-serialized, from the Redis path) is set. +/// +public sealed record CachedItemResult(Item? Item, string? Json, bool CacheHit); + +public sealed record Fortune(int Id, string Message); diff --git a/frameworks/aspnet-minimal-satoriGC/aspnet-minimal-satoriGC.csproj b/frameworks/aspnet-minimal-satoriGC/aspnet-minimal-satoriGC.csproj new file mode 100644 index 000000000..ce4062eb9 --- /dev/null +++ b/frameworks/aspnet-minimal-satoriGC/aspnet-minimal-satoriGC.csproj @@ -0,0 +1,12 @@ + + + + net10.0 + enable + enable + + + + + + diff --git a/frameworks/aspnet-minimal-satoriGC/build.sh b/frameworks/aspnet-minimal-satoriGC/build.sh new file mode 100644 index 000000000..1c42bc2e3 --- /dev/null +++ b/frameworks/aspnet-minimal-satoriGC/build.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +ROOT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" +docker build -t httparena-aspnet-minimal-satoriGC -f "$SCRIPT_DIR/Dockerfile" "$ROOT_DIR" diff --git a/frameworks/aspnet-minimal-satoriGC/meta.json b/frameworks/aspnet-minimal-satoriGC/meta.json new file mode 100644 index 000000000..0df18eb8f --- /dev/null +++ b/frameworks/aspnet-minimal-satoriGC/meta.json @@ -0,0 +1,39 @@ +{ + "display_name": "aspnet-minimal", + "language": "C#", + "type": "experimental", + "mode": "standard", + "completeness": { + "routing": true, + "middleware": true, + "request": true, + "response": true + }, + "engine": "kestrel", + "description": "Minimal ASP.NET Core server using .NET 10 with Kestrel and minimal API routing.", + "repo": "https://github.com/dotnet/aspnetcore", + "enabled": true, + "tls_check": true, + "tests": [ + "baseline", + "pipelined", + "limited-conn", + "async", + "latency-1m", + "latency-10k", + "latency-500k-8cpu", + "json-comp", + "json-tls", + "8gbit", + "static-tls", + "async-db", + "fortunes", + "baseline-h2", + "static-h2", + "baseline-h2c", + "json-h2c", + "baseline-h3", + "static-h3" + ], + "maintainers": [] +} From c0264e2dcfcf7ed208923221823afff6a4dc48ae Mon Sep 17 00:00:00 2001 From: wuuer Date: Wed, 14 Oct 2026 10:25:35 +0800 Subject: [PATCH 2/5] Fix Docker image name case in build script --- frameworks/aspnet-minimal-satoriGC/build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frameworks/aspnet-minimal-satoriGC/build.sh b/frameworks/aspnet-minimal-satoriGC/build.sh index 1c42bc2e3..d2dd0f3ba 100644 --- a/frameworks/aspnet-minimal-satoriGC/build.sh +++ b/frameworks/aspnet-minimal-satoriGC/build.sh @@ -2,4 +2,4 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" ROOT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" -docker build -t httparena-aspnet-minimal-satoriGC -f "$SCRIPT_DIR/Dockerfile" "$ROOT_DIR" +docker build -t httparena-aspnet-minimal-satorigc -f "$SCRIPT_DIR/Dockerfile" "$ROOT_DIR" From ab7bab9edd982fbd81c8a3defa6b924e327dda58 Mon Sep 17 00:00:00 2001 From: Jeff JF Wu Date: Wed, 14 Oct 2026 16:12:43 +0800 Subject: [PATCH 3/5] Make frameworks/aspnet-minimal-satoriGC/build.sh executable --- frameworks/aspnet-minimal-satoriGC/build.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 frameworks/aspnet-minimal-satoriGC/build.sh diff --git a/frameworks/aspnet-minimal-satoriGC/build.sh b/frameworks/aspnet-minimal-satoriGC/build.sh old mode 100644 new mode 100755 From bc127544f7995ca96014a6a07b5dffa35c1907e6 Mon Sep 17 00:00:00 2001 From: MDA2AV Date: Wed, 14 Oct 2026 21:33:51 +0100 Subject: [PATCH 4/5] aspnet-minimal-satorigc: lowercase the directory, own display_name Docker only accepts lowercase image names, and validate.sh and benchmark.sh name the image httparena-. With the directory called aspnet-minimal-satoriGC, `docker run` rejected the name before any test ran; lowercasing the tag in build.sh only fixed the build step. display_name was "aspnet-minimal", the stock entry's name. Results are filed by display name, so a saved benchmark run would have overwritten aspnet-minimal's published numbers with this entry's. --- .../Dockerfile | 2 +- .../Handlers.cs | 0 .../Pages/Fortunes.cshtml | 0 .../Pages/Fortunes.cshtml.cs | 0 .../Program.cs | 0 .../README.md | 2 +- .../RotatingCertificate.cs | 0 .../Services/DatabaseService.cs | 0 .../Services/DatasetService.cs | 0 .../Services/FortuneService.cs | 0 .../Services/ItemService.cs | 0 .../Types/AppJsonContext.cs | 0 .../Types/Types.cs | 0 .../aspnet-minimal-satoriGC.csproj | 0 .../build.sh | 0 .../meta.json | 4 ++-- 16 files changed, 4 insertions(+), 4 deletions(-) rename frameworks/{aspnet-minimal-satoriGC => aspnet-minimal-satorigc}/Dockerfile (93%) rename frameworks/{aspnet-minimal-satoriGC => aspnet-minimal-satorigc}/Handlers.cs (100%) rename frameworks/{aspnet-minimal-satoriGC => aspnet-minimal-satorigc}/Pages/Fortunes.cshtml (100%) rename frameworks/{aspnet-minimal-satoriGC => aspnet-minimal-satorigc}/Pages/Fortunes.cshtml.cs (100%) rename frameworks/{aspnet-minimal-satoriGC => aspnet-minimal-satorigc}/Program.cs (100%) rename frameworks/{aspnet-minimal-satoriGC => aspnet-minimal-satorigc}/README.md (99%) rename frameworks/{aspnet-minimal-satoriGC => aspnet-minimal-satorigc}/RotatingCertificate.cs (100%) rename frameworks/{aspnet-minimal-satoriGC => aspnet-minimal-satorigc}/Services/DatabaseService.cs (100%) rename frameworks/{aspnet-minimal-satoriGC => aspnet-minimal-satorigc}/Services/DatasetService.cs (100%) rename frameworks/{aspnet-minimal-satoriGC => aspnet-minimal-satorigc}/Services/FortuneService.cs (100%) rename frameworks/{aspnet-minimal-satoriGC => aspnet-minimal-satorigc}/Services/ItemService.cs (100%) rename frameworks/{aspnet-minimal-satoriGC => aspnet-minimal-satorigc}/Types/AppJsonContext.cs (100%) rename frameworks/{aspnet-minimal-satoriGC => aspnet-minimal-satorigc}/Types/Types.cs (100%) rename frameworks/{aspnet-minimal-satoriGC => aspnet-minimal-satorigc}/aspnet-minimal-satoriGC.csproj (100%) rename frameworks/{aspnet-minimal-satoriGC => aspnet-minimal-satorigc}/build.sh (100%) rename frameworks/{aspnet-minimal-satoriGC => aspnet-minimal-satorigc}/meta.json (85%) diff --git a/frameworks/aspnet-minimal-satoriGC/Dockerfile b/frameworks/aspnet-minimal-satorigc/Dockerfile similarity index 93% rename from frameworks/aspnet-minimal-satoriGC/Dockerfile rename to frameworks/aspnet-minimal-satorigc/Dockerfile index e8ccd4c19..282ba21cd 100644 --- a/frameworks/aspnet-minimal-satoriGC/Dockerfile +++ b/frameworks/aspnet-minimal-satorigc/Dockerfile @@ -1,6 +1,6 @@ FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build WORKDIR /app -COPY frameworks/aspnet-minimal-satoriGC/ . +COPY frameworks/aspnet-minimal-satorigc/ . RUN dotnet publish -c Release -r linux-x64 --self-contained -o out FROM mcr.microsoft.com/dotnet/aspnet:10.0 diff --git a/frameworks/aspnet-minimal-satoriGC/Handlers.cs b/frameworks/aspnet-minimal-satorigc/Handlers.cs similarity index 100% rename from frameworks/aspnet-minimal-satoriGC/Handlers.cs rename to frameworks/aspnet-minimal-satorigc/Handlers.cs diff --git a/frameworks/aspnet-minimal-satoriGC/Pages/Fortunes.cshtml b/frameworks/aspnet-minimal-satorigc/Pages/Fortunes.cshtml similarity index 100% rename from frameworks/aspnet-minimal-satoriGC/Pages/Fortunes.cshtml rename to frameworks/aspnet-minimal-satorigc/Pages/Fortunes.cshtml diff --git a/frameworks/aspnet-minimal-satoriGC/Pages/Fortunes.cshtml.cs b/frameworks/aspnet-minimal-satorigc/Pages/Fortunes.cshtml.cs similarity index 100% rename from frameworks/aspnet-minimal-satoriGC/Pages/Fortunes.cshtml.cs rename to frameworks/aspnet-minimal-satorigc/Pages/Fortunes.cshtml.cs diff --git a/frameworks/aspnet-minimal-satoriGC/Program.cs b/frameworks/aspnet-minimal-satorigc/Program.cs similarity index 100% rename from frameworks/aspnet-minimal-satoriGC/Program.cs rename to frameworks/aspnet-minimal-satorigc/Program.cs diff --git a/frameworks/aspnet-minimal-satoriGC/README.md b/frameworks/aspnet-minimal-satorigc/README.md similarity index 99% rename from frameworks/aspnet-minimal-satoriGC/README.md rename to frameworks/aspnet-minimal-satorigc/README.md index c916a9d8b..5e8797095 100644 --- a/frameworks/aspnet-minimal-satoriGC/README.md +++ b/frameworks/aspnet-minimal-satorigc/README.md @@ -1,4 +1,4 @@ -# aspnet-minimal +# aspnet-minimal-satorigc Minimal ASP.NET Core HTTP server using .NET 10 with Kestrel, minimal API routing and Satori GC. diff --git a/frameworks/aspnet-minimal-satoriGC/RotatingCertificate.cs b/frameworks/aspnet-minimal-satorigc/RotatingCertificate.cs similarity index 100% rename from frameworks/aspnet-minimal-satoriGC/RotatingCertificate.cs rename to frameworks/aspnet-minimal-satorigc/RotatingCertificate.cs diff --git a/frameworks/aspnet-minimal-satoriGC/Services/DatabaseService.cs b/frameworks/aspnet-minimal-satorigc/Services/DatabaseService.cs similarity index 100% rename from frameworks/aspnet-minimal-satoriGC/Services/DatabaseService.cs rename to frameworks/aspnet-minimal-satorigc/Services/DatabaseService.cs diff --git a/frameworks/aspnet-minimal-satoriGC/Services/DatasetService.cs b/frameworks/aspnet-minimal-satorigc/Services/DatasetService.cs similarity index 100% rename from frameworks/aspnet-minimal-satoriGC/Services/DatasetService.cs rename to frameworks/aspnet-minimal-satorigc/Services/DatasetService.cs diff --git a/frameworks/aspnet-minimal-satoriGC/Services/FortuneService.cs b/frameworks/aspnet-minimal-satorigc/Services/FortuneService.cs similarity index 100% rename from frameworks/aspnet-minimal-satoriGC/Services/FortuneService.cs rename to frameworks/aspnet-minimal-satorigc/Services/FortuneService.cs diff --git a/frameworks/aspnet-minimal-satoriGC/Services/ItemService.cs b/frameworks/aspnet-minimal-satorigc/Services/ItemService.cs similarity index 100% rename from frameworks/aspnet-minimal-satoriGC/Services/ItemService.cs rename to frameworks/aspnet-minimal-satorigc/Services/ItemService.cs diff --git a/frameworks/aspnet-minimal-satoriGC/Types/AppJsonContext.cs b/frameworks/aspnet-minimal-satorigc/Types/AppJsonContext.cs similarity index 100% rename from frameworks/aspnet-minimal-satoriGC/Types/AppJsonContext.cs rename to frameworks/aspnet-minimal-satorigc/Types/AppJsonContext.cs diff --git a/frameworks/aspnet-minimal-satoriGC/Types/Types.cs b/frameworks/aspnet-minimal-satorigc/Types/Types.cs similarity index 100% rename from frameworks/aspnet-minimal-satoriGC/Types/Types.cs rename to frameworks/aspnet-minimal-satorigc/Types/Types.cs diff --git a/frameworks/aspnet-minimal-satoriGC/aspnet-minimal-satoriGC.csproj b/frameworks/aspnet-minimal-satorigc/aspnet-minimal-satoriGC.csproj similarity index 100% rename from frameworks/aspnet-minimal-satoriGC/aspnet-minimal-satoriGC.csproj rename to frameworks/aspnet-minimal-satorigc/aspnet-minimal-satoriGC.csproj diff --git a/frameworks/aspnet-minimal-satoriGC/build.sh b/frameworks/aspnet-minimal-satorigc/build.sh similarity index 100% rename from frameworks/aspnet-minimal-satoriGC/build.sh rename to frameworks/aspnet-minimal-satorigc/build.sh diff --git a/frameworks/aspnet-minimal-satoriGC/meta.json b/frameworks/aspnet-minimal-satorigc/meta.json similarity index 85% rename from frameworks/aspnet-minimal-satoriGC/meta.json rename to frameworks/aspnet-minimal-satorigc/meta.json index 0df18eb8f..9cf07ab77 100644 --- a/frameworks/aspnet-minimal-satoriGC/meta.json +++ b/frameworks/aspnet-minimal-satorigc/meta.json @@ -1,5 +1,5 @@ { - "display_name": "aspnet-minimal", + "display_name": "aspnet-minimal-satorigc", "language": "C#", "type": "experimental", "mode": "standard", @@ -10,7 +10,7 @@ "response": true }, "engine": "kestrel", - "description": "Minimal ASP.NET Core server using .NET 10 with Kestrel and minimal API routing.", + "description": "Minimal ASP.NET Core server using .NET 10 with Kestrel and minimal API routing, published self-contained with the Satori GC.", "repo": "https://github.com/dotnet/aspnetcore", "enabled": true, "tls_check": true, From 46caea2b0d81f55778cb5e5e33fb56e0e9064937 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 14 Oct 2026 21:10:13 +0000 Subject: [PATCH 5/5] Benchmark results: aspnet-minimal-satorigc [skip ci] --- site/data/frameworks.json | 16 +- .../data/results/aspnet-minimal-satorigc.json | 507 ++++++++++++++++++ .../8gbit/512/aspnet-minimal-satorigc.log | 0 .../async-db/1024/aspnet-minimal-satorigc.log | 2 + .../async/32000/aspnet-minimal-satorigc.log | 0 .../1024/aspnet-minimal-satorigc.log | 0 .../256/aspnet-minimal-satorigc.log | 0 .../1024/aspnet-minimal-satorigc.log | 0 .../256/aspnet-minimal-satorigc.log | 0 .../4096/aspnet-minimal-satorigc.log | 0 .../64/aspnet-minimal-satorigc.log | 0 .../baseline/4096/aspnet-minimal-satorigc.log | 0 .../fortunes/1024/aspnet-minimal-satorigc.log | 2 + .../16384/aspnet-minimal-satorigc.log | 0 .../4096/aspnet-minimal-satorigc.log | 0 .../json-h2c/1024/aspnet-minimal-satorigc.log | 0 .../json-h2c/4096/aspnet-minimal-satorigc.log | 0 .../json-tls/4096/aspnet-minimal-satorigc.log | 0 .../1024/aspnet-minimal-satorigc.log | 0 .../1024/aspnet-minimal-satorigc.log | 0 .../1024/aspnet-minimal-satorigc.log | 0 .../4096/aspnet-minimal-satorigc.log | 0 .../4096/aspnet-minimal-satorigc.log | 0 .../1024/aspnet-minimal-satorigc.log | 0 .../static-h2/256/aspnet-minimal-satorigc.log | 0 .../static-h3/64/aspnet-minimal-satorigc.log | 0 .../1024/aspnet-minimal-satorigc.log | 0 27 files changed, 526 insertions(+), 1 deletion(-) create mode 100644 site/data/results/aspnet-minimal-satorigc.json create mode 100644 site/static/logs/8gbit/512/aspnet-minimal-satorigc.log create mode 100644 site/static/logs/async-db/1024/aspnet-minimal-satorigc.log create mode 100644 site/static/logs/async/32000/aspnet-minimal-satorigc.log create mode 100644 site/static/logs/baseline-h2/1024/aspnet-minimal-satorigc.log create mode 100644 site/static/logs/baseline-h2/256/aspnet-minimal-satorigc.log create mode 100644 site/static/logs/baseline-h2c/1024/aspnet-minimal-satorigc.log create mode 100644 site/static/logs/baseline-h2c/256/aspnet-minimal-satorigc.log create mode 100644 site/static/logs/baseline-h2c/4096/aspnet-minimal-satorigc.log create mode 100644 site/static/logs/baseline-h3/64/aspnet-minimal-satorigc.log create mode 100644 site/static/logs/baseline/4096/aspnet-minimal-satorigc.log create mode 100644 site/static/logs/fortunes/1024/aspnet-minimal-satorigc.log create mode 100644 site/static/logs/json-comp/16384/aspnet-minimal-satorigc.log create mode 100644 site/static/logs/json-comp/4096/aspnet-minimal-satorigc.log create mode 100644 site/static/logs/json-h2c/1024/aspnet-minimal-satorigc.log create mode 100644 site/static/logs/json-h2c/4096/aspnet-minimal-satorigc.log create mode 100644 site/static/logs/json-tls/4096/aspnet-minimal-satorigc.log create mode 100644 site/static/logs/latency-10k/1024/aspnet-minimal-satorigc.log create mode 100644 site/static/logs/latency-1m/1024/aspnet-minimal-satorigc.log create mode 100644 site/static/logs/latency-500k-8cpu/1024/aspnet-minimal-satorigc.log create mode 100644 site/static/logs/limited-conn/4096/aspnet-minimal-satorigc.log create mode 100644 site/static/logs/pipelined/4096/aspnet-minimal-satorigc.log create mode 100644 site/static/logs/static-h2/1024/aspnet-minimal-satorigc.log create mode 100644 site/static/logs/static-h2/256/aspnet-minimal-satorigc.log create mode 100644 site/static/logs/static-h3/64/aspnet-minimal-satorigc.log create mode 100644 site/static/logs/static-tls/1024/aspnet-minimal-satorigc.log diff --git a/site/data/frameworks.json b/site/data/frameworks.json index e6dbe86eb..adcc439d3 100644 --- a/site/data/frameworks.json +++ b/site/data/frameworks.json @@ -142,6 +142,20 @@ "response": true } }, + "aspnet-minimal-satorigc": { + "dir": "aspnet-minimal-satorigc", + "description": "Minimal ASP.NET Core server using .NET 10 with Kestrel and minimal API routing, published self-contained with the Satori GC.", + "repo": "https://github.com/dotnet/aspnetcore", + "type": "experimental", + "engine": "kestrel", + "mode": "standard", + "completeness": { + "routing": true, + "middleware": true, + "request": true, + "response": true + } + }, "aspnet-minimal + caddy": { "dir": "aspnet-minimal_caddy", "description": "ASP.NET minimal API behind Caddy, serving HTTP/3 (QUIC) at the edge with h1 forwarding to Kestrel.", @@ -2568,4 +2582,4 @@ } ] } -} +} \ No newline at end of file diff --git a/site/data/results/aspnet-minimal-satorigc.json b/site/data/results/aspnet-minimal-satorigc.json new file mode 100644 index 000000000..c679a6b02 --- /dev/null +++ b/site/data/results/aspnet-minimal-satorigc.json @@ -0,0 +1,507 @@ +{ + "framework": "aspnet-minimal-satorigc", + "results": { + "8gbit-512": { + "framework": "aspnet-minimal-satorigc", + "language": "C#", + "rps": 49359, + "avg_latency": "143.1us", + "p99_latency": "198.0us", + "cpu": "963.5%", + "memory": "291MiB", + "connections": 512, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "512.50MB/s", + "input_bw": "482.02MB/s", + "reconnects": 0, + "status_2xx": 246887, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0, + "cpu_usec": 48044400, + "cpu_per_req_us": 194.6008, + "target_rate": 50000, + "rate_ratio": 0.9872, + "p99_9_latency": "3117.0us" + }, + "async-32000": { + "framework": "aspnet-minimal-satorigc", + "language": "C#", + "rps": 893944, + "avg_latency": "29.96ms", + "p99_latency": "58.50ms", + "cpu": "4640.2%", + "memory": "1.3GiB", + "connections": 32000, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "137.22MB/s", + "input_bw": "40.92MB/s", + "reconnects": 0, + "status_2xx": 8939443, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "async-db-1024": { + "framework": "aspnet-minimal-satorigc", + "language": "C#", + "rps": 191739, + "avg_latency": "4.67ms", + "p99_latency": "9.91ms", + "cpu": "3561.1%", + "memory": "459MiB", + "connections": 1024, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "742.63MB/s", + "input_bw": "12.80MB/s", + "reconnects": 76557, + "status_2xx": 1917395, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "baseline-4096": { + "framework": "aspnet-minimal-satorigc", + "language": "C#", + "rps": 1219116, + "avg_latency": "2.82ms", + "p99_latency": "8.64ms", + "cpu": "4996.7%", + "memory": "491MiB", + "connections": 4096, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "187.14MB/s", + "input_bw": "94.17MB/s", + "reconnects": 0, + "status_2xx": 6095584, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "baseline-h2-1024": { + "framework": "aspnet-minimal-satorigc", + "language": "C#", + "rps": 2143886, + "avg_latency": "44.62ms", + "p99_latency": "182.00ms", + "cpu": "5950.6%", + "memory": "3.7GiB", + "connections": 1024, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "49.72MB/s", + "reconnects": 0, + "status_2xx": 10848068, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "baseline-h2-256": { + "framework": "aspnet-minimal-satorigc", + "language": "C#", + "rps": 2036196, + "avg_latency": "11.80ms", + "p99_latency": "67.49ms", + "cpu": "5853.4%", + "memory": "1.2GiB", + "connections": 256, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "46.90MB/s", + "reconnects": 0, + "status_2xx": 10242068, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "baseline-h2c-1024": { + "framework": "aspnet-minimal-satorigc", + "language": "C#", + "rps": 2596218, + "avg_latency": "39.15ms", + "p99_latency": "153.34ms", + "cpu": "6037.4%", + "memory": "3.3GiB", + "connections": 1024, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "57.57MB/s", + "reconnects": 0, + "status_2xx": 13110902, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "baseline-h2c-256": { + "framework": "aspnet-minimal-satorigc", + "language": "C#", + "rps": 2498402, + "avg_latency": "9.93ms", + "p99_latency": "48.69ms", + "cpu": "5732.7%", + "memory": "1.1GiB", + "connections": 256, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "55.14MB/s", + "reconnects": 0, + "status_2xx": 12566964, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "baseline-h2c-4096": { + "framework": "aspnet-minimal-satorigc", + "language": "C#", + "rps": 2664426, + "avg_latency": "116.75ms", + "p99_latency": "473.15ms", + "cpu": "6081.0%", + "memory": "9.4GiB", + "connections": 4096, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "59.80MB/s", + "reconnects": 0, + "status_2xx": 13588577, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "baseline-h3-64": { + "framework": "aspnet-minimal-satorigc", + "language": "C#", + "rps": 565429, + "avg_latency": "6.53ms", + "p99_latency": "18.36ms", + "cpu": "6084.3%", + "memory": "1.3GiB", + "connections": 64, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "58.35MB/s", + "reconnects": 0, + "status_2xx": 2832803, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "fortunes-1024": { + "framework": "aspnet-minimal-satorigc", + "language": "C#", + "rps": 96661, + "avg_latency": "7.85ms", + "p99_latency": "21.60ms", + "cpu": "4788.8%", + "memory": "781MiB", + "connections": 1024, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "2.31GB/s", + "reconnects": 0, + "status_2xx": 483306, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "json-comp-16384": { + "framework": "aspnet-minimal-satorigc", + "language": "C#", + "rps": 345749, + "avg_latency": "17.01ms", + "p99_latency": "47.80ms", + "cpu": "5533.7%", + "memory": "778MiB", + "connections": 16384, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "666.27MB/s", + "input_bw": "25.72MB/s", + "reconnects": 65548, + "status_2xx": 1728749, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "json-comp-4096": { + "framework": "aspnet-minimal-satorigc", + "language": "C#", + "rps": 349215, + "avg_latency": "8.50ms", + "p99_latency": "27.10ms", + "cpu": "5759.4%", + "memory": "582MiB", + "connections": 4096, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "672.84MB/s", + "input_bw": "25.98MB/s", + "reconnects": 68022, + "status_2xx": 1746076, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "json-h2c-1024": { + "framework": "aspnet-minimal-satorigc", + "language": "C#", + "rps": 773740, + "avg_latency": "41.47ms", + "p99_latency": "207.95ms", + "cpu": "6008.3%", + "memory": "2.1GiB", + "connections": 1024, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "2.58GB/s", + "reconnects": 0, + "status_2xx": 3907389, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "json-h2c-4096": { + "framework": "aspnet-minimal-satorigc", + "language": "C#", + "rps": 778502, + "avg_latency": "125.95ms", + "p99_latency": "613.60ms", + "cpu": "6050.0%", + "memory": "5.6GiB", + "connections": 4096, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "2.61GB/s", + "reconnects": 0, + "status_2xx": 3962580, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "json-tls-4096": { + "framework": "aspnet-minimal-satorigc", + "language": "C#", + "rps": 457277, + "avg_latency": "8.46ms", + "p99_latency": "85.75ms", + "cpu": "5973.2%", + "memory": "936MiB", + "connections": 4096, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "1.57GB", + "reconnects": 0, + "status_2xx": 2332223, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "latency-10k-1024": { + "framework": "aspnet-minimal-satorigc", + "language": "C#", + "rps": 9983, + "avg_latency": "84.2us", + "p99_latency": "122.0us", + "cpu": "144.5%", + "memory": "163MiB", + "connections": 1024, + "threads": 64, + "duration": "20s", + "pipeline": 1, + "bandwidth": "1.60MB/s", + "reconnects": 0, + "status_2xx": 199683, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0, + "cpu_usec": 30042113, + "cpu_per_req_us": 150.449, + "target_rate": 10000, + "rate_ratio": 0.9983, + "p99_9_latency": "792.0us" + }, + "latency-1m-1024": { + "framework": "aspnet-minimal-satorigc", + "language": "C#", + "rps": 997843, + "avg_latency": "967.7us", + "p99_latency": "5066.0us", + "cpu": "4274.0%", + "memory": "286MiB", + "connections": 1024, + "threads": 64, + "duration": "20s", + "pipeline": 1, + "bandwidth": "159.65MB/s", + "reconnects": 0, + "status_2xx": 19960774, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0, + "cpu_usec": 808631403, + "cpu_per_req_us": 40.511, + "target_rate": 1000000, + "rate_ratio": 0.9978, + "p99_9_latency": "12140.0us" + }, + "latency-500k-8cpu-1024": { + "framework": "aspnet-minimal-satorigc", + "language": "C#", + "rps": 294754, + "avg_latency": "4107749.8us", + "p99_latency": "8087552.0us", + "cpu": "747.4%", + "memory": "148MiB", + "connections": 1024, + "threads": 64, + "duration": "20s", + "pipeline": 1, + "bandwidth": "47.16MB/s", + "reconnects": 0, + "status_2xx": 5897209, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0, + "cpu_usec": 149921037, + "cpu_per_req_us": 25.4224, + "target_rate": 500000, + "rate_ratio": 0.5895, + "p99_9_latency": "8173568.0us" + }, + "limited-conn-4096": { + "framework": "aspnet-minimal-satorigc", + "language": "C#", + "rps": 457215, + "avg_latency": "1.44ms", + "p99_latency": "12.70ms", + "cpu": "3342.0%", + "memory": "408MiB", + "connections": 4096, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "70.18MB/s", + "input_bw": "35.32MB/s", + "reconnects": 228598, + "status_2xx": 2286075, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "pipelined-4096": { + "framework": "aspnet-minimal-satorigc", + "language": "C#", + "rps": 5796816, + "avg_latency": "8.01ms", + "p99_latency": "25.00ms", + "cpu": "5244.4%", + "memory": "516MiB", + "connections": 4096, + "threads": 64, + "duration": "5s", + "pipeline": 16, + "bandwidth": "889.72MB/s", + "reconnects": 0, + "status_2xx": 28984082, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "static-h2-1024": { + "framework": "aspnet-minimal-satorigc", + "language": "C#", + "rps": 60293, + "avg_latency": "483.62ms", + "p99_latency": "4.87s", + "cpu": "6462.1%", + "memory": "8.8GiB", + "connections": 1024, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "1.49GB/s", + "reconnects": 0, + "status_2xx": 305086, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "static-h2-256": { + "framework": "aspnet-minimal-satorigc", + "language": "C#", + "rps": 75945, + "avg_latency": "105.21ms", + "p99_latency": "945.47ms", + "cpu": "6216.2%", + "memory": "3.4GiB", + "connections": 256, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "2.00GB/s", + "reconnects": 0, + "status_2xx": 382004, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "static-h3-64": { + "framework": "aspnet-minimal-satorigc", + "language": "C#", + "rps": 42181, + "avg_latency": "80.08ms", + "p99_latency": "340.55ms", + "cpu": "6109.0%", + "memory": "2.3GiB", + "connections": 64, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "1.11GB/s", + "reconnects": 0, + "status_2xx": 211328, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "static-tls-1024": { + "framework": "aspnet-minimal-satorigc", + "language": "C#", + "rps": 89039, + "avg_latency": "11.70ms", + "p99_latency": "70.07ms", + "cpu": "5939.2%", + "memory": "713MiB", + "connections": 1024, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "2.39GB", + "reconnects": 0, + "status_2xx": 452740, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + } + } +} diff --git a/site/static/logs/8gbit/512/aspnet-minimal-satorigc.log b/site/static/logs/8gbit/512/aspnet-minimal-satorigc.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/async-db/1024/aspnet-minimal-satorigc.log b/site/static/logs/async-db/1024/aspnet-minimal-satorigc.log new file mode 100644 index 000000000..ec73cd531 --- /dev/null +++ b/site/static/logs/async-db/1024/aspnet-minimal-satorigc.log @@ -0,0 +1,2 @@ +Cannot load library libgssapi_krb5.so.2 +Error: libgssapi_krb5.so.2: cannot open shared object file: No such file or directory diff --git a/site/static/logs/async/32000/aspnet-minimal-satorigc.log b/site/static/logs/async/32000/aspnet-minimal-satorigc.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/baseline-h2/1024/aspnet-minimal-satorigc.log b/site/static/logs/baseline-h2/1024/aspnet-minimal-satorigc.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/baseline-h2/256/aspnet-minimal-satorigc.log b/site/static/logs/baseline-h2/256/aspnet-minimal-satorigc.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/baseline-h2c/1024/aspnet-minimal-satorigc.log b/site/static/logs/baseline-h2c/1024/aspnet-minimal-satorigc.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/baseline-h2c/256/aspnet-minimal-satorigc.log b/site/static/logs/baseline-h2c/256/aspnet-minimal-satorigc.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/baseline-h2c/4096/aspnet-minimal-satorigc.log b/site/static/logs/baseline-h2c/4096/aspnet-minimal-satorigc.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/baseline-h3/64/aspnet-minimal-satorigc.log b/site/static/logs/baseline-h3/64/aspnet-minimal-satorigc.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/baseline/4096/aspnet-minimal-satorigc.log b/site/static/logs/baseline/4096/aspnet-minimal-satorigc.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/fortunes/1024/aspnet-minimal-satorigc.log b/site/static/logs/fortunes/1024/aspnet-minimal-satorigc.log new file mode 100644 index 000000000..ec73cd531 --- /dev/null +++ b/site/static/logs/fortunes/1024/aspnet-minimal-satorigc.log @@ -0,0 +1,2 @@ +Cannot load library libgssapi_krb5.so.2 +Error: libgssapi_krb5.so.2: cannot open shared object file: No such file or directory diff --git a/site/static/logs/json-comp/16384/aspnet-minimal-satorigc.log b/site/static/logs/json-comp/16384/aspnet-minimal-satorigc.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/json-comp/4096/aspnet-minimal-satorigc.log b/site/static/logs/json-comp/4096/aspnet-minimal-satorigc.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/json-h2c/1024/aspnet-minimal-satorigc.log b/site/static/logs/json-h2c/1024/aspnet-minimal-satorigc.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/json-h2c/4096/aspnet-minimal-satorigc.log b/site/static/logs/json-h2c/4096/aspnet-minimal-satorigc.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/json-tls/4096/aspnet-minimal-satorigc.log b/site/static/logs/json-tls/4096/aspnet-minimal-satorigc.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/latency-10k/1024/aspnet-minimal-satorigc.log b/site/static/logs/latency-10k/1024/aspnet-minimal-satorigc.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/latency-1m/1024/aspnet-minimal-satorigc.log b/site/static/logs/latency-1m/1024/aspnet-minimal-satorigc.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/latency-500k-8cpu/1024/aspnet-minimal-satorigc.log b/site/static/logs/latency-500k-8cpu/1024/aspnet-minimal-satorigc.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/limited-conn/4096/aspnet-minimal-satorigc.log b/site/static/logs/limited-conn/4096/aspnet-minimal-satorigc.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/pipelined/4096/aspnet-minimal-satorigc.log b/site/static/logs/pipelined/4096/aspnet-minimal-satorigc.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/static-h2/1024/aspnet-minimal-satorigc.log b/site/static/logs/static-h2/1024/aspnet-minimal-satorigc.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/static-h2/256/aspnet-minimal-satorigc.log b/site/static/logs/static-h2/256/aspnet-minimal-satorigc.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/static-h3/64/aspnet-minimal-satorigc.log b/site/static/logs/static-h3/64/aspnet-minimal-satorigc.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/static-tls/1024/aspnet-minimal-satorigc.log b/site/static/logs/static-tls/1024/aspnet-minimal-satorigc.log new file mode 100644 index 000000000..e69de29bb