diff --git a/frameworks/CSharp/genhttp/Benchmarks/Benchmarks.csproj b/frameworks/CSharp/genhttp/Benchmarks/Benchmarks.csproj index a9e3397bed8..896e4f2f86c 100644 --- a/frameworks/CSharp/genhttp/Benchmarks/Benchmarks.csproj +++ b/frameworks/CSharp/genhttp/Benchmarks/Benchmarks.csproj @@ -10,31 +10,70 @@ GenHTTP Benchmarks Test suite to be executed with TechEmpower FrameworkBenchmarks. - $(DefineConstants);$(GENHTTP_ENGINE_NAME) - + $(GENHTTP_ENGINE_NAME) + true true + false + + + + + + + + + + + - - + + - + + + + + + + + + + + - + + + + + + + + + + + + + + + + + - + - - + + + diff --git a/frameworks/CSharp/genhttp/Benchmarks/Model/Database.cs b/frameworks/CSharp/genhttp/Benchmarks/Model/Database.cs new file mode 100644 index 00000000000..e1335a014ed --- /dev/null +++ b/frameworks/CSharp/genhttp/Benchmarks/Model/Database.cs @@ -0,0 +1,17 @@ +namespace Benchmarks.Model; + +using Npgsql; + +public static class Database +{ + + public static readonly NpgsqlDataSource DataSource = BuildDataSource(); + + private static NpgsqlDataSource BuildDataSource() + { + var connectionString = Environment.GetEnvironmentVariable("DB_CONNECTION"); + + return new NpgsqlSlimDataSourceBuilder(connectionString).EnableArrays().Build(); + } + +} diff --git a/frameworks/CSharp/genhttp/Benchmarks/Model/DatabaseContext.cs b/frameworks/CSharp/genhttp/Benchmarks/Model/DatabaseContext.cs deleted file mode 100644 index f27f1d8e245..00000000000 --- a/frameworks/CSharp/genhttp/Benchmarks/Model/DatabaseContext.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Microsoft.EntityFrameworkCore; - -namespace Benchmarks.Model; - -public sealed class DatabaseContext : DbContext -{ - private static DbContextOptions _options; - - private static DbContextOptions _noTrackingOptions; - - #region Factory - - public static DatabaseContext Create() => new(_options ??= GetOptions(true)); - - public static DatabaseContext CreateNoTracking() => new(_noTrackingOptions ??= GetOptions(false)); - - private static DbContextOptions GetOptions(bool tracking) - { - var optionsBuilder = new DbContextOptionsBuilder(); - - optionsBuilder.UseNpgsql("Server=tfb-database;Database=hello_world;User Id=benchmarkdbuser;Password=benchmarkdbpass;SSL Mode=Disable;Maximum Pool Size=512;NoResetOnClose=true;Enlist=false;Max Auto Prepare=4"); - - if (!tracking) - { - optionsBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); - } - - return optionsBuilder.Options; - } - - private DatabaseContext(DbContextOptions options) : base(options) { } - - #endregion - - #region Entities - - public DbSet World { get; set; } - - public DbSet Fortune { get; set; } - - #endregion - -} diff --git a/frameworks/CSharp/genhttp/Benchmarks/Model/FortuneMap.cs b/frameworks/CSharp/genhttp/Benchmarks/Model/FortuneMap.cs new file mode 100644 index 00000000000..cf80593b746 --- /dev/null +++ b/frameworks/CSharp/genhttp/Benchmarks/Model/FortuneMap.cs @@ -0,0 +1,80 @@ +using System.Collections; +using Cottle; + +namespace Benchmarks.Model; + +public readonly struct FortuneMap : IMap +{ + private readonly int _id; + private readonly string _message; + + public FortuneMap(int id, string message) + { + _id = id; + _message = message; + } + + public Value this[Value key] => TryGet(key, out var value) ? value : Value.Undefined; + + public int Count => 2; + + public bool Contains(Value key) + { + var keyStr = key.AsString; + return keyStr == "id" || keyStr == "message"; + } + + public bool TryGet(Value key, out Value value) + { + var keyStr = key.AsString; + if (keyStr == "id") + { + value = _id; + return true; + } + if (keyStr == "message") + { + value = _message; + return true; + } + + value = Value.Undefined; + return false; + } + + public int CompareTo(IMap other) + { + if (other == null) return 1; + + if (other.TryGet("id", out var otherId)) + { + return _id.CompareTo(otherId.AsNumber); + } + + return Count.CompareTo(other.Count); + } + + public bool Equals(IMap other) + { + if (other == null || other.Count != Count) + return false; + + return other.TryGet("id", out var otherId) && + other.TryGet("message", out var otherMsg) && + _id == (int)otherId.AsNumber && + _message == otherMsg.AsString; + } + + public IEnumerator> GetEnumerator() + { + yield return new KeyValuePair("id", _id); + yield return new KeyValuePair("message", _message); + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public override bool Equals(object obj) => obj is IMap map && Equals(map); + + public override int GetHashCode() => HashCode.Combine(_id, _message); + +} \ No newline at end of file diff --git a/frameworks/CSharp/genhttp/Benchmarks/Program.Internal.cs b/frameworks/CSharp/genhttp/Benchmarks/Program.Internal.cs new file mode 100644 index 00000000000..3813f5ae438 --- /dev/null +++ b/frameworks/CSharp/genhttp/Benchmarks/Program.Internal.cs @@ -0,0 +1,9 @@ +using Benchmarks; + +using GenHTTP.Engine.Internal; + +var project = Project.Create(); + +return await Host.Create() + .Handler(project) + .RunAsync(); diff --git a/frameworks/CSharp/genhttp/Benchmarks/Program.Kestrel.cs b/frameworks/CSharp/genhttp/Benchmarks/Program.Kestrel.cs new file mode 100644 index 00000000000..41779a8abc8 --- /dev/null +++ b/frameworks/CSharp/genhttp/Benchmarks/Program.Kestrel.cs @@ -0,0 +1,9 @@ +using Benchmarks; + +using GenHTTP.Engine.Kestrel; + +var project = Project.Create(); + +return await Host.Create() + .Handler(project) + .RunAsync(); diff --git a/frameworks/CSharp/genhttp/Benchmarks/Program.Unhinged.cs b/frameworks/CSharp/genhttp/Benchmarks/Program.Unhinged.cs new file mode 100644 index 00000000000..f5d91eee4a5 --- /dev/null +++ b/frameworks/CSharp/genhttp/Benchmarks/Program.Unhinged.cs @@ -0,0 +1,17 @@ +using Benchmarks; + +using Unhinged; +using Unhinged.GenHttp.Experimental; + +var project = Project.Create(); + +UnhingedEngine.CreateBuilder() + .SetPort(8080) + .SetNWorkersSolver(() => Environment.ProcessorCount) + .SetBacklog(16384) + .SetMaxEventsPerWake(512) + .SetMaxNumberConnectionsPerWorker(1024) + .SetSlabSizes(32 * 1024, 16 * 1024) + .Map(project) + .Build() + .Run(); diff --git a/frameworks/CSharp/genhttp/Benchmarks/Program.Wired.cs b/frameworks/CSharp/genhttp/Benchmarks/Program.Wired.cs new file mode 100644 index 00000000000..b46e28cada0 --- /dev/null +++ b/frameworks/CSharp/genhttp/Benchmarks/Program.Wired.cs @@ -0,0 +1,14 @@ +using Benchmarks; + +using GenHTTP.Adapters.WiredIO; + +using Wired.IO.App; + +var project = Project.Create(); + +await WiredApp.CreateExpressBuilder() + .Port(8080) + .MapGenHttp("*", project) + .Build() + .RunAsync(); + \ No newline at end of file diff --git a/frameworks/CSharp/genhttp/Benchmarks/Program.cs b/frameworks/CSharp/genhttp/Benchmarks/Program.cs deleted file mode 100644 index 91323662c2d..00000000000 --- a/frameworks/CSharp/genhttp/Benchmarks/Program.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Benchmarks.Tests; -using Benchmarks.Utilities; - -#if INTERNAL -using GenHTTP.Engine.Internal; -#else -using GenHTTP.Engine.Kestrel; -#endif - -using GenHTTP.Modules.IO; -using GenHTTP.Modules.Layouting; -using GenHTTP.Modules.Webservices; - -var tests = Layout.Create() - .Add("plaintext", Content.From(Resource.FromString("Hello, World!"))) - .Add("json", new JsonHandler()) - .Add("fortunes", new FortuneHandler()) - .AddService("db") - .AddService("queries") - .AddService("updates") - .AddService("cached-worlds") - .Add(ServerHeader.Create()); - -return await Host.Create() - .Handler(tests) - .RunAsync(); diff --git a/frameworks/CSharp/genhttp/Benchmarks/Project.cs b/frameworks/CSharp/genhttp/Benchmarks/Project.cs new file mode 100644 index 00000000000..13bae801b07 --- /dev/null +++ b/frameworks/CSharp/genhttp/Benchmarks/Project.cs @@ -0,0 +1,31 @@ +using Benchmarks.Tests; +using Benchmarks.Utilities; + +using GenHTTP.Api.Content; + +using GenHTTP.Modules.IO; +using GenHTTP.Modules.Layouting; +using GenHTTP.Modules.Reflection; +using GenHTTP.Modules.Webservices; + +namespace Benchmarks; + +public static class Project +{ + + public static IHandlerBuilder Create() + { + var mode = ExecutionMode.Auto; + + return Layout.Create() + .Add("plaintext", Content.From(Resource.FromString("Hello, World!"))) + .Add("json", new JsonHandler()) + .Add("fortunes", new FortuneHandler()) + .AddService("db", mode: mode) + .AddService("queries", mode: mode) + .AddService("updates", mode: mode) + .AddService("cached-worlds", mode: mode) + .Add(ServerHeader.Create()); + } + +} diff --git a/frameworks/CSharp/genhttp/Benchmarks/Tests/CacheResource.cs b/frameworks/CSharp/genhttp/Benchmarks/Tests/CacheResource.cs index e0e4bc50d91..cb7a5a96215 100644 --- a/frameworks/CSharp/genhttp/Benchmarks/Tests/CacheResource.cs +++ b/frameworks/CSharp/genhttp/Benchmarks/Tests/CacheResource.cs @@ -1,20 +1,26 @@ -using Benchmarks.Model; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +using Benchmarks.Model; + using GenHTTP.Modules.Webservices; -using Microsoft.EntityFrameworkCore; + using Microsoft.Extensions.Caching.Memory; +using Npgsql; + namespace Benchmarks.Tests; public sealed class CacheResource { - private static readonly Random Random = new(); - private static readonly MemoryCache Cache = new(new MemoryCacheOptions { ExpirationScanFrequency = TimeSpan.FromMinutes(60) }); - private static readonly object[] CacheKeys = Enumerable.Range(0, 10001).Select(i => new CacheKey(i)).ToArray(); + private static readonly CacheKey[] CacheKeys = Enumerable.Range(0, 10001).Select(i => new CacheKey(i)).ToArray(); [ResourceMethod(":queries")] public ValueTask> GetWorldsFromPath(string queries) => GetWorlds(queries); @@ -22,9 +28,10 @@ public sealed class CacheResource [ResourceMethod] public async ValueTask> GetWorlds(string queries) { - var count = 1; - - int.TryParse(queries, out count); + if (!int.TryParse(queries, out var count)) + { + count = 1; + } if (count < 1) { @@ -35,13 +42,13 @@ public async ValueTask> GetWorlds(string queries) count = 500; } - var result = new List(count); + NpgsqlConnection connection = null; - await using var context = DatabaseContext.CreateNoTracking(); + var result = new List(count); for (var i = 0; i < count; i++) { - var id = Random.Next(1, 10001); + var id = Random.Shared.Next(1, 10001); var key = CacheKeys[id]; @@ -53,7 +60,12 @@ public async ValueTask> GetWorlds(string queries) } else { - var resolved = await context.World.FirstOrDefaultAsync(w => w.Id == id).ConfigureAwait(false); + if (connection == null) + { + connection = await Database.DataSource.OpenConnectionAsync(); + } + + var resolved = await GetWorldById(connection, id); Cache.Set(key, resolved); @@ -64,6 +76,28 @@ public async ValueTask> GetWorlds(string queries) return result; } + private static async Task GetWorldById(NpgsqlConnection connection, int id) + { + await using var command = connection.CreateCommand(); + + command.CommandText = "SELECT id, randomnumber FROM world WHERE id = @Id"; + + command.Parameters.AddWithValue("@Id", id); + + await using var reader = await command.ExecuteReaderAsync(); + + if (await reader.ReadAsync()) + { + return new() + { + Id = reader.GetInt32(0), + RandomNumber = reader.GetInt32(1) + }; + } + + return null; + } + public sealed class CacheKey : IEquatable { private readonly int _value; @@ -81,4 +115,5 @@ public CacheKey(int value) public override string ToString() => _value.ToString(); } + } diff --git a/frameworks/CSharp/genhttp/Benchmarks/Tests/DbResource.cs b/frameworks/CSharp/genhttp/Benchmarks/Tests/DbResource.cs index ac33808fdfd..e7363d438b4 100644 --- a/frameworks/CSharp/genhttp/Benchmarks/Tests/DbResource.cs +++ b/frameworks/CSharp/genhttp/Benchmarks/Tests/DbResource.cs @@ -1,21 +1,36 @@ using Benchmarks.Model; using GenHTTP.Modules.Webservices; -using Microsoft.EntityFrameworkCore; namespace Benchmarks.Tests; public sealed class DbResource { - private static readonly Random Random = new(); [ResourceMethod] - public async ValueTask GetRandomWorld() + public Task GetRandomWorld() => GetWorldById(Random.Shared.Next(1, 10001)); + + private static async Task GetWorldById(int id) { - var id = Random.Next(1, 10001); + await using var connection = await Database.DataSource.OpenConnectionAsync(); + + await using var command = connection.CreateCommand(); + + command.CommandText = "SELECT id, randomnumber FROM world WHERE id = @Id"; + + command.Parameters.AddWithValue("@Id", id); + + await using var reader = await command.ExecuteReaderAsync(); - await using var context = DatabaseContext.CreateNoTracking(); + if (await reader.ReadAsync()) + { + return new() + { + Id = reader.GetInt32(0), + RandomNumber = reader.GetInt32(1) + }; + } - return await context.World.FirstOrDefaultAsync(w => w.Id == id).ConfigureAwait(false); + return null; } } diff --git a/frameworks/CSharp/genhttp/Benchmarks/Tests/FortuneHandler.cs b/frameworks/CSharp/genhttp/Benchmarks/Tests/FortuneHandler.cs index c7a488f5e78..b3b3fab47f4 100644 --- a/frameworks/CSharp/genhttp/Benchmarks/Tests/FortuneHandler.cs +++ b/frameworks/CSharp/genhttp/Benchmarks/Tests/FortuneHandler.cs @@ -1,85 +1,106 @@ -using System.Web; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using System.Web; using Benchmarks.Model; + using Cottle; + using GenHTTP.Api.Content; using GenHTTP.Api.Protocol; using GenHTTP.Modules.IO; using GenHTTP.Modules.Pages; -using GenHTTP.Modules.Pages.Rendering; - -using Microsoft.EntityFrameworkCore; namespace Benchmarks.Tests; public class FortuneHandler : IHandler { + private IDocument _template; - #region Get-/Setters - - private TemplateRenderer Template { get; } - - #endregion - - #region Initialization + #region Functionality - public FortuneHandler() + public async ValueTask PrepareAsync() { var resource = Resource.FromAssembly("Template.html").Build(); - Template = Renderer.From(resource); + using var reader = new StreamReader(await resource.GetContentAsync()); + + _template = Document.CreateDefault(reader).DocumentOrThrow; } - #endregion - - #region Functionality - - public ValueTask PrepareAsync() => new(); - public async ValueTask HandleAsync(IRequest request) { - var data = new Dictionary + if (_template == null) { - ["cookies"] = Value.FromEnumerable(await GetFortunes()) - }; + await PrepareAsync(); + } + + var template = _template ?? throw new InvalidOperationException("Template has not been initialized"); + + var fortunes = await GetFortunes(); + + var context = BuildContext(fortunes); - return request.GetPage(await Template.RenderAsync(data)).Build(); + var content = template.Render(context); + + return request.GetPage(content).Build(); } - private static async ValueTask> GetFortunes() + private static async Task> GetFortunes() { - await using var context = DatabaseContext.CreateNoTracking(); + var fortunes = await QueryDatabaseAsync(); + + fortunes.Add(new() { Id = 0, Message = "Additional fortune added at request time." }); + + fortunes.Sort((x, y) => string.CompareOrdinal(x.Message, y.Message)); + + return fortunes; + } - var fortunes = await context.Fortune.ToListAsync().ConfigureAwait(false); + private static async Task> QueryDatabaseAsync() + { + await using var connection = await Database.DataSource.OpenConnectionAsync(); - var result = new List(fortunes.Count + 1); + await using var command = connection.CreateCommand(); - foreach (var fortune in fortunes) + command.CommandText = "SELECT id, message FROM fortune"; + + var result = new List(16); + + await using var reader = await command.ExecuteReaderAsync(); + + while (await reader.ReadAsync()) { - result.Add(Value.FromDictionary(new Dictionary() + result.Add(new () { - ["id"] = fortune.Id, - ["message"] = HttpUtility.HtmlEncode(fortune.Message) - })); + Id = reader.GetInt32(0), + Message = HttpUtility.HtmlEncode(reader.GetString(1)) + }); } - result.Add(Value.FromDictionary(new Dictionary() - { - ["id"] = 0, - ["message"] = "Additional fortune added at request time." - })); - - result.Sort((one, two) => - { - var firstMessage = one.Fields["message"].AsString; - var secondMessage = two.Fields["message"].AsString; - - return string.Compare(firstMessage, secondMessage, StringComparison.Ordinal); - }); - return result; } + private static IContext BuildContext(List cookies) + { + var values = new Value[cookies.Count]; + + for (var i = 0; i < cookies.Count; i++) + { + values[i] = Value.FromMap(new FortuneMap(cookies[i].Id, cookies[i].Message)); + } + + var store = new Dictionary + { + ["cookies"] = Value.FromEnumerable(values.ToArray()) + }; + + return Context.CreateBuiltin(store); + } + #endregion - + } diff --git a/frameworks/CSharp/genhttp/Benchmarks/Tests/QueryResource.cs b/frameworks/CSharp/genhttp/Benchmarks/Tests/QueryResource.cs index 592d9083242..3bf10aef302 100644 --- a/frameworks/CSharp/genhttp/Benchmarks/Tests/QueryResource.cs +++ b/frameworks/CSharp/genhttp/Benchmarks/Tests/QueryResource.cs @@ -1,22 +1,28 @@ -using Benchmarks.Model; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +using Benchmarks.Model; + using GenHTTP.Modules.Webservices; -using Microsoft.EntityFrameworkCore; + +using NpgsqlTypes; namespace Benchmarks.Tests; public sealed class QueryResource { - private static readonly Random Random = new(); [ResourceMethod(":queries")] - public ValueTask> GetWorldsFromPath(string queries) => GetWorlds(queries); + public Task> GetWorldsFromPath(string queries) => GetWorlds(queries); [ResourceMethod] - public async ValueTask> GetWorlds(string queries) + public Task> GetWorlds(string queries) { - var count = 1; - - int.TryParse(queries, out count); + if (!int.TryParse(queries, out var count)) + { + count = 1; + } if (count < 1) { @@ -27,18 +33,38 @@ public async ValueTask> GetWorlds(string queries) count = 500; } + return GetRandomWorlds(count); + } + + private static async Task> GetRandomWorlds(int count) + { var result = new List(count); - using var context = DatabaseContext.CreateNoTracking(); + await using var connection = await Database.DataSource.OpenConnectionAsync(); + + await using var command = connection.CreateCommand(); + + command.CommandText = "SELECT id, randomnumber FROM world WHERE id = @Id"; - for (var _ = 0; _ < count; _++) + var parameter = command.Parameters.Add("@Id", NpgsqlDbType.Integer); + + for (var i = 0; i < count; i++) { - var id = Random.Next(1, 10001); + parameter.Value = Random.Shared.Next(1, 10001); - result.Add(await context.World.FirstOrDefaultAsync(w => w.Id == id).ConfigureAwait(false)); - } + await using var reader = await command.ExecuteReaderAsync(); + if (await reader.ReadAsync()) + { + result.Add(new() + { + Id = reader.GetInt32(0), + RandomNumber = reader.GetInt32(1) + }); + } + } + return result; } -} \ No newline at end of file +} diff --git a/frameworks/CSharp/genhttp/Benchmarks/Tests/UpdateResource.cs b/frameworks/CSharp/genhttp/Benchmarks/Tests/UpdateResource.cs index 9b2ce8e270c..deb58893776 100644 --- a/frameworks/CSharp/genhttp/Benchmarks/Tests/UpdateResource.cs +++ b/frameworks/CSharp/genhttp/Benchmarks/Tests/UpdateResource.cs @@ -1,22 +1,28 @@ -using Benchmarks.Model; +using System; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using Benchmarks.Model; + using GenHTTP.Modules.Webservices; -using Microsoft.EntityFrameworkCore; + +using Npgsql; +using NpgsqlTypes; namespace Benchmarks.Tests; public sealed class UpdateResource { - private static readonly Random Random = new(); [ResourceMethod(":queries")] - public ValueTask> UpdateWorldsFromPath(string queries) => UpdateWorlds(queries); + public ValueTask UpdateWorldsFromPath(string queries) => UpdateWorlds(queries); [ResourceMethod] - public async ValueTask> UpdateWorlds(string queries) + public ValueTask UpdateWorlds(string queries) { - var count = 1; - - int.TryParse(queries, out count); + if (!int.TryParse(queries, out var count)) + { + count = 1; + } if (count < 1) { @@ -26,39 +32,87 @@ public async ValueTask> UpdateWorlds(string queries) { count = 500; } + + return FetchAndUpdateWorlds(count); + } - var result = new List(count); + private async ValueTask FetchAndUpdateWorlds(int count) + { + var results = new World[count]; - var ids = Enumerable.Range(1, 10000).Select(x => Random.Next(1, 10001)).Distinct().Take(count).ToArray(); + var ids = new int[count]; + var numbers = new int[count]; - using (var context = DatabaseContext.Create()) + for (var i = 0; i < count; i++) + { + ids[i] = Random.Shared.Next(1, 10001); + } + + Array.Sort(ids); + + for (var i = 1; i < count; i++) + if (ids[i] == ids[i - 1]) + ids[i] = (ids[i] % 10000) + 1; + + using var connection = await Database.DataSource.OpenConnectionAsync(); + + // Each row must be selected randomly using one query in the same fashion as the single database query test + // Use of IN clauses or similar means to consolidate multiple queries into one operation is not permitted. + // Similarly, use of a batch or multiple SELECTs within a single statement are not permitted + var (queryCmd, queryParameter) = CreateReadCommand(connection); + using (queryCmd) { - foreach (var id in ids) + for (var i = 0; i < results.Length; i++) { - var record = await context.World.FirstOrDefaultAsync(w => w.Id == id).ConfigureAwait(false); + queryParameter.TypedValue = ids[i]; + results[i] = await ReadSingleRow(queryCmd); + } + } - var old = record.RandomNumber; + for (var i = 0; i < count; i++) + { + var randomNumber = Random.Shared.Next(1, 10001); + if (results[i].RandomNumber == randomNumber) + { + randomNumber = (randomNumber % 10000) + 1; + } - var current = old; + results[i].RandomNumber = randomNumber; + numbers[i] = randomNumber; + } - for (var i = 0; i < 5; i++) - { - current = Random.Next(1, 10001); + var update = "UPDATE world w SET randomnumber = u.new_val FROM (SELECT unnest($1) as id, unnest($2) as new_val) u WHERE w.id = u.id"; - if (current != old) - { - break; - } - } + using var updateCmd = new NpgsqlCommand(update, connection); + updateCmd.Parameters.Add(new NpgsqlParameter { TypedValue = ids, NpgsqlDbType = NpgsqlDbType.Array | NpgsqlDbType.Integer }); + updateCmd.Parameters.Add(new NpgsqlParameter { TypedValue = numbers, NpgsqlDbType = NpgsqlDbType.Array | NpgsqlDbType.Integer }); - record.RandomNumber = current; + await updateCmd.ExecuteNonQueryAsync(); - result.Add(record); + return results; + } + + private (NpgsqlCommand readCmd, NpgsqlParameter idParameter) CreateReadCommand(NpgsqlConnection connection) + { + var cmd = new NpgsqlCommand("SELECT id, randomnumber FROM world WHERE id = $1", connection); + var parameter = new NpgsqlParameter { TypedValue = Random.Shared.Next(1, 10001) }; - await context.SaveChangesAsync(); - } - } + cmd.Parameters.Add(parameter); + + return (cmd, parameter); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static async Task ReadSingleRow(NpgsqlCommand cmd) + { + using var rdr = await cmd.ExecuteReaderAsync(System.Data.CommandBehavior.SingleRow); + await rdr.ReadAsync(); - return result; + return new World + { + Id = rdr.GetInt32(0), + RandomNumber = rdr.GetInt32(1) + }; } -} \ No newline at end of file + +} diff --git a/frameworks/CSharp/genhttp/benchmark_config.json b/frameworks/CSharp/genhttp/benchmark_config.json index f2376bf64fc..0f024d1b5ba 100644 --- a/frameworks/CSharp/genhttp/benchmark_config.json +++ b/frameworks/CSharp/genhttp/benchmark_config.json @@ -45,6 +45,50 @@ "database_os": "Linux", "display_name": "genhttp [kestrel]", "notes": "" + }, + "wired": { + "plaintext_url": "/plaintext", + "json_url": "/json", + "db_url": "/db", + "query_url": "/queries/", + "update_url": "/updates/", + "fortune_url": "/fortunes", + "cached_query_url": "/cached-worlds/", + "port": 8080, + "approach": "Realistic", + "classification": "Fullstack", + "database": "Postgres", + "framework": "GenHTTP", + "language": "C#", + "orm": "Raw", + "platform": ".NET", + "webserver": "Wired.IO", + "os": "Linux", + "database_os": "Linux", + "display_name": "genhttp [wired.io]", + "notes": "" + }, + "unhinged": { + "plaintext_url": "/plaintext", + "json_url": "/json", + "db_url": "/db", + "query_url": "/queries/", + "update_url": "/updates/", + "fortune_url": "/fortunes", + "cached_query_url": "/cached-worlds/", + "port": 8080, + "approach": "Realistic", + "classification": "Fullstack", + "database": "Postgres", + "framework": "GenHTTP", + "language": "C#", + "orm": "Raw", + "platform": ".NET", + "webserver": "Unhinged", + "os": "Linux", + "database_os": "Linux", + "display_name": "genhttp [unhinged]", + "notes": "" } }] } diff --git a/frameworks/CSharp/genhttp/config.toml b/frameworks/CSharp/genhttp/config.toml index 538213c05b4..2338b765a36 100644 --- a/frameworks/CSharp/genhttp/config.toml +++ b/frameworks/CSharp/genhttp/config.toml @@ -36,3 +36,39 @@ orm = "Raw" platform = ".NET" webserver = "Kestrel" versus = "None" + +[wired] +urls.plaintext = "/plaintext" +urls.json = "/json" +urls.db = "/db" +urls.query = "/queries/" +urls.update = "/updates/" +urls.fortune = "/fortunes" +urls.cached_query = "/cached-worlds/" +approach = "Realistic" +classification = "Fullstack" +database = "Postgres" +database_os = "Linux" +os = "Linux" +orm = "Raw" +platform = ".NET" +webserver = "Wired.IO" +versus = "None" + +[unhinged] +urls.plaintext = "/plaintext" +urls.json = "/json" +urls.db = "/db" +urls.query = "/queries/" +urls.update = "/updates/" +urls.fortune = "/fortunes" +urls.cached_query = "/cached-worlds/" +approach = "Realistic" +classification = "Fullstack" +database = "Postgres" +database_os = "Linux" +os = "Linux" +orm = "Raw" +platform = ".NET" +webserver = "Unhinged" +versus = "None" diff --git a/frameworks/CSharp/genhttp/genhttp-kestrel.dockerfile b/frameworks/CSharp/genhttp/genhttp-kestrel.dockerfile index e345a538f34..bc85d2b34e0 100644 --- a/frameworks/CSharp/genhttp/genhttp-kestrel.dockerfile +++ b/frameworks/CSharp/genhttp/genhttp-kestrel.dockerfile @@ -1,8 +1,7 @@ FROM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build WORKDIR /source -ENV GENHTTP_ENGINE_NAME=KESTREL -ENV GENHTTP_ENGINE_PACKAGE=GenHTTP.Core.Kestrel +ENV GENHTTP_ENGINE_NAME=Kestrel # copy csproj and restore as distinct layers COPY Benchmarks/*.csproj . @@ -17,13 +16,12 @@ FROM mcr.microsoft.com/dotnet/runtime-deps:10.0-alpine ENV DOTNET_GCDynamicAdaptationMode=0 \ DOTNET_EnableDiagnostics=0 \ - COMPlus_EnableDiagnostics=0 \ - COMPlus_DbgEnableMiniDump=0 \ - COMPlus_DbgEnableMiniDumpCollection=0 \ - COMPlus_DbgMiniDumpType=0 \ - DOTNET_TieredPGO=0 \ - DOTNET_TC_QuickJitForLoops=1 \ - DOTNET_TC_QuickJit=1 + DOTNET_TieredPGO=1 \ + DOTNET_TC_QuickJitForLoops=0 \ + DOTNET_ReadyToRun=0 \ + DOTNET_TieredCompilation=0 \ + DOTNET_gcServer=1 \ + DB_CONNECTION="Server=tfb-database;Database=hello_world;User Id=benchmarkdbuser;Password=benchmarkdbpass;SSL Mode=Disable;Maximum Pool Size=18;Enlist=false;Max Auto Prepare=4;Multiplexing=true;Write Coalescing Buffer Threshold Bytes=1000;" WORKDIR /app COPY --from=build /app . diff --git a/frameworks/CSharp/genhttp/genhttp-unhinged.dockerfile b/frameworks/CSharp/genhttp/genhttp-unhinged.dockerfile new file mode 100644 index 00000000000..ba540aa46d0 --- /dev/null +++ b/frameworks/CSharp/genhttp/genhttp-unhinged.dockerfile @@ -0,0 +1,31 @@ +FROM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build +WORKDIR /source + +ENV GENHTTP_ENGINE_NAME=Unhinged + +# copy csproj and restore as distinct layers +COPY Benchmarks/*.csproj . +RUN dotnet restore -r linux-musl-x64 + +# copy and publish app and libraries +COPY Benchmarks/ . +RUN dotnet publish -c release -o /app -r linux-musl-x64 --no-restore --self-contained + +# final stage/image +FROM mcr.microsoft.com/dotnet/runtime-deps:10.0-alpine + +ENV DOTNET_GCDynamicAdaptationMode=0 \ + DOTNET_EnableDiagnostics=0 \ + DOTNET_TieredPGO=1 \ + DOTNET_TC_QuickJitForLoops=0 \ + DOTNET_ReadyToRun=0 \ + DOTNET_TieredCompilation=0 \ + DOTNET_gcServer=1 \ + DB_CONNECTION="Server=tfb-database;Database=hello_world;User Id=benchmarkdbuser;Password=benchmarkdbpass;SSL Mode=Disable;Maximum Pool Size=18;Enlist=false;Max Auto Prepare=4;Multiplexing=true;Write Coalescing Buffer Threshold Bytes=1000;" + +WORKDIR /app +COPY --from=build /app . + +ENTRYPOINT ["./Benchmarks"] + +EXPOSE 8080 diff --git a/frameworks/CSharp/genhttp/genhttp-wired.dockerfile b/frameworks/CSharp/genhttp/genhttp-wired.dockerfile new file mode 100644 index 00000000000..9011e6ef2f0 --- /dev/null +++ b/frameworks/CSharp/genhttp/genhttp-wired.dockerfile @@ -0,0 +1,31 @@ +FROM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build +WORKDIR /source + +ENV GENHTTP_ENGINE_NAME=Wired + +# copy csproj and restore as distinct layers +COPY Benchmarks/*.csproj . +RUN dotnet restore -r linux-musl-x64 + +# copy and publish app and libraries +COPY Benchmarks/ . +RUN dotnet publish -c release -o /app -r linux-musl-x64 --no-restore --self-contained + +# final stage/image +FROM mcr.microsoft.com/dotnet/runtime-deps:10.0-alpine + +ENV DOTNET_GCDynamicAdaptationMode=0 \ + DOTNET_EnableDiagnostics=0 \ + DOTNET_TieredPGO=1 \ + DOTNET_TC_QuickJitForLoops=0 \ + DOTNET_ReadyToRun=0 \ + DOTNET_TieredCompilation=0 \ + DOTNET_gcServer=1 \ + DB_CONNECTION="Server=tfb-database;Database=hello_world;User Id=benchmarkdbuser;Password=benchmarkdbpass;SSL Mode=Disable;Maximum Pool Size=18;Enlist=false;Max Auto Prepare=4;Multiplexing=true;Write Coalescing Buffer Threshold Bytes=1000;" + +WORKDIR /app +COPY --from=build /app . + +ENTRYPOINT ["./Benchmarks"] + +EXPOSE 8080 diff --git a/frameworks/CSharp/genhttp/genhttp.dockerfile b/frameworks/CSharp/genhttp/genhttp.dockerfile index 9414123202e..2280d4c2b53 100644 --- a/frameworks/CSharp/genhttp/genhttp.dockerfile +++ b/frameworks/CSharp/genhttp/genhttp.dockerfile @@ -1,8 +1,7 @@ FROM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build WORKDIR /source -ENV GENHTTP_ENGINE_NAME=INTERNAL -ENV GENHTTP_ENGINE_PACKAGE=GenHTTP.Core +ENV GENHTTP_ENGINE_NAME=Internal # copy csproj and restore as distinct layers COPY Benchmarks/*.csproj . @@ -17,13 +16,12 @@ FROM mcr.microsoft.com/dotnet/runtime-deps:10.0-alpine ENV DOTNET_GCDynamicAdaptationMode=0 \ DOTNET_EnableDiagnostics=0 \ - COMPlus_EnableDiagnostics=0 \ - COMPlus_DbgEnableMiniDump=0 \ - COMPlus_DbgEnableMiniDumpCollection=0 \ - COMPlus_DbgMiniDumpType=0 \ - DOTNET_TieredPGO=0 \ - DOTNET_TC_QuickJitForLoops=1 \ - DOTNET_TC_QuickJit=1 + DOTNET_TieredPGO=1 \ + DOTNET_TC_QuickJitForLoops=0 \ + DOTNET_ReadyToRun=0 \ + DOTNET_TieredCompilation=0 \ + DOTNET_gcServer=1 \ + DB_CONNECTION="Server=tfb-database;Database=hello_world;User Id=benchmarkdbuser;Password=benchmarkdbpass;SSL Mode=Disable;Maximum Pool Size=18;Enlist=false;Max Auto Prepare=4;Multiplexing=true;Write Coalescing Buffer Threshold Bytes=1000;" WORKDIR /app COPY --from=build /app . diff --git a/frameworks/CSharp/wiredio/UnhGHttp/Program.cs b/frameworks/CSharp/wiredio/UnhGHttp/Program.cs deleted file mode 100644 index 03d8a523ff3..00000000000 --- a/frameworks/CSharp/wiredio/UnhGHttp/Program.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Text.Json; -using GenHTTP.Api.Protocol; -using GenHTTP.Modules.IO; -using GenHTTP.Modules.Layouting; -using GenHTTP.Modules.Layouting.Provider; -using Unhinged; -using Unhinged.GenHttp.Experimental; - -internal class Program -{ - public static void Main(string[] args) - { - var builder = UnhingedEngine - .CreateBuilder() - .SetNWorkersSolver(() => Environment.ProcessorCount) - .SetBacklog(16384) - .SetMaxEventsPerWake(512) - .SetMaxNumberConnectionsPerWorker(512) - .SetPort(8080) - .SetSlabSizes(32 * 1024, 16 * 1024) - .Map(CreateLayoutBuilder()); - - var engine = builder.Build(); - engine.Run(); - } - - private static LayoutBuilder CreateLayoutBuilder() => - Layout - .Create() - .Add("/plaintext", Content.From(Resource.FromString("Hello, World!"))) - - .Add("/json", Content.From( - Resource.FromString(JsonSerializer.Serialize(new JsonMessage { message = "Hello, World!" })) - .Type(new FlexibleContentType("application/json")))); -} - -public class JsonMessage -{ - public string message { get; set; } -} \ No newline at end of file diff --git a/frameworks/CSharp/wiredio/UnhGHttp/UnhGHttp.csproj b/frameworks/CSharp/wiredio/UnhGHttp/UnhGHttp.csproj deleted file mode 100644 index 09308ff4747..00000000000 --- a/frameworks/CSharp/wiredio/UnhGHttp/UnhGHttp.csproj +++ /dev/null @@ -1,23 +0,0 @@ - - - - Exe - net9.0 - enable - enable - - true - true - true - true - - linux-musl-x64 - - - - - - - - - diff --git a/frameworks/CSharp/wiredio/benchmark_config.json b/frameworks/CSharp/wiredio/benchmark_config.json index 2d9ebf4896f..dd173b57d0b 100644 --- a/frameworks/CSharp/wiredio/benchmark_config.json +++ b/frameworks/CSharp/wiredio/benchmark_config.json @@ -53,23 +53,6 @@ "database_os": "Linux", "display_name": "Unhinged [p]", "notes": "epoll" - }, - "gen": { - "plaintext_url": "/plaintext", - "json_url": "/json", - "port": 8080, - "approach": "Realistic", - "classification": "Fullstack", - "database": "None", - "framework": "GenHttp", - "language": "C#", - "orm": "None", - "platform": ".NET", - "webserver": "Unhinged", - "os": "Linux", - "database_os": "Linux", - "display_name": "genhttp [Unhinged]", - "notes": "Adapter" } } ] diff --git a/frameworks/CSharp/wiredio/config.toml b/frameworks/CSharp/wiredio/config.toml index 0b973f1f1fb..c0e5b1efa44 100644 --- a/frameworks/CSharp/wiredio/config.toml +++ b/frameworks/CSharp/wiredio/config.toml @@ -36,15 +36,3 @@ orm = "None" platform = ".NET" webserver = "Unhinged" versus = "None" - -[gen] -urls.plaintext = "/plaintext" -urls.json = "/json" -approach = "Realistic" -classification = "Fullstack" -os = "Linux" -database_os = "Linux" -orm = "None" -platform = ".NET" -webserver = "Unhinged" -versus = "None" \ No newline at end of file diff --git a/frameworks/CSharp/wiredio/wiredio-gen.dockerfile b/frameworks/CSharp/wiredio/wiredio-gen.dockerfile deleted file mode 100644 index 5436527f964..00000000000 --- a/frameworks/CSharp/wiredio/wiredio-gen.dockerfile +++ /dev/null @@ -1,24 +0,0 @@ -FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine AS build -WORKDIR /source - -# copy csproj and restore as distinct layers -COPY UnhGHttp/*.csproj . -RUN dotnet restore -r linux-musl-x64 - -# copy and publish app and libraries -COPY UnhGHttp/ . -RUN dotnet publish -c release -o /app -r linux-musl-x64 --no-restore --self-contained - -# final stage/image -FROM mcr.microsoft.com/dotnet/runtime-deps:9.0-alpine - -ENV DOTNET_GCDynamicAdaptationMode=0 -ENV DOTNET_ReadyToRun=0 -ENV DOTNET_HillClimbing_Disable=1 - -WORKDIR /app -COPY --from=build /app . - -ENTRYPOINT ["./UnhGHttp"] - -EXPOSE 8080