diff --git a/frameworks/CSharp/appmpower/README.md b/frameworks/CSharp/appmpower/README.md new file mode 100644 index 00000000000..75ff21e1637 --- /dev/null +++ b/frameworks/CSharp/appmpower/README.md @@ -0,0 +1,31 @@ +# [appMpower](https://github.com/LLT21/)(.Net) Benchmarking Test +This includes tests for plaintext, json, db, queries, updates and fortune. + +[`appMpower`](https://github.com/LLT21/) is a nativily compiled (AOT) .NET implementation. The native compilation is done with reflection disabled; because the most used PostgreSQL .NET library is not reflection free, the PostgreSQL ODBC driver is used instead. + +## Infrastructure Software Versions + +**Language** + +* C# 7.0 + +**Platforms** + +* .NET Core (Windows and Linux) + +**Web Servers** + +* [Kestrel](https://github.com/aspnet/KestrelHttpServer) + +**Web Stack** + +* ASP.NET Core + +## Paths & Source for Tests + +* [Plaintext](Benchmarks/Program.cs): "/plaintext" +* [JSON Serialization](Benchmarks/Program.cs): "/json" +* [Single query](Benchmarks/Program.cs): "/db" +* [Multiple query](Benchmarks/Program.cs): "/queries" +* [Updates](Benchmarks/Program.cs): "/updates" +* [Fortune](Benchmarks/Program.cs): "/fortunes" diff --git a/frameworks/CSharp/appmpower/appmpower.dockerfile b/frameworks/CSharp/appmpower/appmpower.dockerfile new file mode 100644 index 00000000000..31bde86e4ff --- /dev/null +++ b/frameworks/CSharp/appmpower/appmpower.dockerfile @@ -0,0 +1,22 @@ +FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build +RUN apt-get update +RUN apt-get -yqq install clang zlib1g-dev libkrb5-dev libtinfo5 + +WORKDIR /app +COPY src . +RUN dotnet publish -c Release -o out -r linux-x64 + +FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS runtime + +RUN apt-get update +RUN apt-get install -y unixodbc odbc-postgresql + +WORKDIR /etc/ +COPY odbcinst.ini . + +WORKDIR /app +COPY --from=build /app/out ./ + +EXPOSE 8080 + +ENTRYPOINT ["./appMpower"] \ No newline at end of file diff --git a/frameworks/CSharp/appmpower/benchmark_config.json b/frameworks/CSharp/appmpower/benchmark_config.json new file mode 100644 index 00000000000..43aa48ba49a --- /dev/null +++ b/frameworks/CSharp/appmpower/benchmark_config.json @@ -0,0 +1,30 @@ +{ + "framework": "appmpower", + "tests": [ + { + "default": { + "plaintext_url": "/plaintext", + "json_url": "/json", + "db_url": "/db", + "query_url": "/queries?c=", + "update_url": "/updates?c=", + "fortune_url": "/fortunes", + "port": 8080, + "approach": "Realistic", + "classification": "Platform", + "database": "Postgres", + "framework": "ASP.NET Core", + "language": "C#", + "orm": "Raw", + "platform": ".NET", + "flavor": "CoreCLR", + "webserver": "Kestrel", + "os": "Linux", + "database_os": "Linux", + "display_name": "appMpower", + "notes": "", + "versus": "aspcore" + } + } + ] +} diff --git a/frameworks/CSharp/appmpower/config.toml b/frameworks/CSharp/appmpower/config.toml new file mode 100644 index 00000000000..37f041ce85e --- /dev/null +++ b/frameworks/CSharp/appmpower/config.toml @@ -0,0 +1,19 @@ +[framework] +name = "appmpower" + +[main] +urls.plaintext = "/plaintext" +urls.json = "/json" +urls.db = "/db" +urls.query = "/queries?c=" +urls.update = "/updates?c=" +urls.fortune = "/fortunes" +approach = "Realistic" +classification = "Platform" +database = "Postgres" +database_os = "Linux" +os = "Linux" +orm = "Raw" +platform = ".NET" +webserver = "kestrel" +versus = "aspcore" diff --git a/frameworks/CSharp/appmpower/odbcinst.ini b/frameworks/CSharp/appmpower/odbcinst.ini new file mode 100644 index 00000000000..afac001e318 --- /dev/null +++ b/frameworks/CSharp/appmpower/odbcinst.ini @@ -0,0 +1,17 @@ +[ODBC] +Trace=1 +Debug=1 +Pooling=No + +[ODBC Drivers] +PostgreSQL = Installed + +; +; odbcinst.ini +; +[PostgreSQL] +Description=ODBC for PostgreSQL +; WARNING: The old psql odbc driver psqlodbc.so is now renamed psqlodbcw.so +; in version 08.x. Note that the library can also be installed under an other +; path than /usr/local/lib/ following your installation. +Driver=/usr/lib/x86_64-linux-gnu/odbc/psqlodbcw.so \ No newline at end of file diff --git a/frameworks/CSharp/appmpower/src/ConnectionStrings.cs b/frameworks/CSharp/appmpower/src/ConnectionStrings.cs new file mode 100644 index 00000000000..c30f676aeae --- /dev/null +++ b/frameworks/CSharp/appmpower/src/ConnectionStrings.cs @@ -0,0 +1,8 @@ +namespace appMpower +{ + public static class ConnectionStrings + { + //public const string OdbcConnection = "Driver={PostgreSQL};Server=host.docker.internal;Port=5432;Database=hello_world;Uid=benchmarkdbuser;Pwd=benchmarkdbpass;UseServerSidePrepare=1;Pooling=false"; + public const string OdbcConnection = "Driver={PostgreSQL};Server=tfb-database;Database=hello_world;Uid=benchmarkdbuser;Pwd=benchmarkdbpass;UseServerSidePrepare=1;Pooling=false"; + } +} \ No newline at end of file diff --git a/frameworks/CSharp/appmpower/src/Db/PooledCommand.cs b/frameworks/CSharp/appmpower/src/Db/PooledCommand.cs new file mode 100644 index 00000000000..78f387dcea7 --- /dev/null +++ b/frameworks/CSharp/appmpower/src/Db/PooledCommand.cs @@ -0,0 +1,211 @@ +using System.Data; +using System.Data.Common; +using System.Data.Odbc; +using System.Threading.Tasks; + +namespace appMpower.Db +{ + public class PooledCommand : IDbCommand + { + private OdbcCommand _odbcCommand; + private PooledConnection _pooledConnection; + + public PooledCommand(PooledConnection pooledConnection) + { + _odbcCommand = (OdbcCommand)pooledConnection.CreateCommand(); + _pooledConnection = pooledConnection; + } + + public PooledCommand(string commandText, PooledConnection pooledConnection) + { + pooledConnection.GetCommand(commandText, this); + } + + internal PooledCommand(OdbcCommand odbcCommand, PooledConnection pooledConnection) + { + _odbcCommand = odbcCommand; + _pooledConnection = pooledConnection; + } + + internal OdbcCommand OdbcCommand + { + get + { + return _odbcCommand; + } + set + { + _odbcCommand = value; + } + } + + internal PooledConnection PooledConnection + { + get + { + return _pooledConnection; + } + set + { + _pooledConnection = value; + } + } + + public string CommandText + { + get + { + return _odbcCommand.CommandText; + } + set + { + _odbcCommand.CommandText = value; + } + } + + public int CommandTimeout + { + get + { + return _odbcCommand.CommandTimeout; + } + set + { + _odbcCommand.CommandTimeout = value; + } + } + public CommandType CommandType + { + get + { + return _odbcCommand.CommandType; + } + set + { + _odbcCommand.CommandType = value; + } + } + +#nullable enable + public IDbConnection? Connection + { + get + { + return _odbcCommand.Connection; + } + set + { + _odbcCommand.Connection = (OdbcConnection?)value; + } + } +#nullable disable + + + public IDataParameterCollection Parameters + { + get + { + return _odbcCommand.Parameters; + } + } + +#nullable enable + public IDbTransaction? Transaction + { + get + { + return _odbcCommand.Transaction; + } + set + { + _odbcCommand.Transaction = (OdbcTransaction?)value; + } + } +#nullable disable + + public UpdateRowSource UpdatedRowSource + { + get + { + return _odbcCommand.UpdatedRowSource; + } + set + { + _odbcCommand.UpdatedRowSource = value; + } + } + public void Cancel() + { + _odbcCommand.Cancel(); + } + + public IDbDataParameter CreateParameter() + { + return _odbcCommand.CreateParameter(); + } + + public IDbDataParameter CreateParameter(string name, DbType dbType, object value) + { + OdbcParameter odbcParameter = null; + + if (this.Parameters.Contains(name)) + { + odbcParameter = (OdbcParameter)this.Parameters[name]; + odbcParameter.Value = value; + } + else + { + odbcParameter = _odbcCommand.CreateParameter(); + + odbcParameter.ParameterName = name; + odbcParameter.DbType = dbType; + odbcParameter.Value = value; + this.Parameters.Add(odbcParameter); + } + + return odbcParameter; + } + + public int ExecuteNonQuery() + { + return _odbcCommand.ExecuteNonQuery(); + } + + public IDataReader ExecuteReader() + { + return _odbcCommand.ExecuteReader(); + } + + public async Task ExecuteNonQueryAsync() + { + return await _odbcCommand.ExecuteNonQueryAsync(); + } + + public async Task ExecuteReaderAsync(CommandBehavior behavior) + { + return await _odbcCommand.ExecuteReaderAsync(behavior); + } + + public IDataReader ExecuteReader(CommandBehavior behavior) + { + return _odbcCommand.ExecuteReader(behavior); + } + +#nullable enable + public object? ExecuteScalar() + { + return _odbcCommand.ExecuteScalar(); + } +#nullable disable + + public void Prepare() + { + _odbcCommand.Prepare(); + } + + public void Dispose() + { + _pooledConnection.ReleaseCommand(this); + } + } +} \ No newline at end of file diff --git a/frameworks/CSharp/appmpower/src/Db/PooledConnection.cs b/frameworks/CSharp/appmpower/src/Db/PooledConnection.cs new file mode 100644 index 00000000000..f6efaf380ab --- /dev/null +++ b/frameworks/CSharp/appmpower/src/Db/PooledConnection.cs @@ -0,0 +1,200 @@ +using System; +using System.Collections.Concurrent; +using System.Data; +using System.Data.Odbc; +using System.Threading.Tasks; + +namespace appMpower.Db +{ + public class PooledConnection : IDbConnection + { + private bool _isInUse = true; + private byte _number = 0; + private OdbcConnection _odbcConnection; + private ConcurrentDictionary _pooledCommands; + + internal PooledConnection() + { + } + + internal PooledConnection(OdbcConnection odbcConnection) + { + _odbcConnection = odbcConnection; + _pooledCommands = new ConcurrentDictionary(); + } + + internal ConcurrentDictionary PooledCommands + { + get + { + return _pooledCommands; + } + set + { + _pooledCommands = value; + } + } + + public bool IsInUse + { + get + { + return _isInUse; + } + set + { + _isInUse = value; + } + } + + public byte Number + { + get + { + return _number; + } + set + { + _number = value; + } + } + + public OdbcConnection OdbcConnection + { + get + { + return _odbcConnection; + } + set + { + _odbcConnection = value; + } + } + + public string ConnectionString + { + get + { + return _odbcConnection.ConnectionString; + } + set + { + _odbcConnection.ConnectionString = value; + } + } + + public int ConnectionTimeout + { + get + { + return _odbcConnection.ConnectionTimeout; + } + } + + public string Database + { + get + { + return _odbcConnection.Database; + } + } + + public ConnectionState State + { + get + { + return _odbcConnection.State; + } + } + + public IDbTransaction BeginTransaction() + { + return _odbcConnection.BeginTransaction(); + } + + public IDbTransaction BeginTransaction(IsolationLevel il) + { + return _odbcConnection.BeginTransaction(il); + } + + public void ChangeDatabase(string databaseName) + { + _odbcConnection.ChangeDatabase(databaseName); + } + + public void Close() + { + PooledConnections.ReleaseConnection(this); + _isInUse = false; + } + + public IDbCommand CreateCommand() + { + return _odbcConnection.CreateCommand(); + } + + public OdbcCommand CreateOdbcCommand() + { + return _odbcConnection.CreateCommand(); + } + + public void Open() + { + if (_odbcConnection.State == ConnectionState.Closed) + { + _odbcConnection.Open(); + } + } + + public void Dispose() + { + if (_isInUse && _odbcConnection.State == ConnectionState.Open) + { + PooledConnections.ReleaseConnection(this); + _isInUse = false; + } + } + + public async Task OpenAsync() + { + if (_odbcConnection.State == ConnectionState.Closed) + { + try + { + await _odbcConnection.OpenAsync(); + } + catch (Exception exception) + { + Console.WriteLine(exception.Message); + } + } + } + + internal PooledCommand GetCommand(string commandText, PooledCommand pooledCommand) + { + PooledCommand internalCommand; + + if (_pooledCommands.TryRemove(commandText, out internalCommand)) + { + pooledCommand.OdbcCommand = internalCommand.OdbcCommand; + pooledCommand.PooledConnection = internalCommand.PooledConnection; + } + else + { + pooledCommand.OdbcCommand = new OdbcCommand(commandText, this.OdbcConnection); + pooledCommand.PooledConnection = this; + _pooledCommands.TryAdd(commandText, pooledCommand); + + //Console.WriteLine("prepare pool connection: " + this._number + " for command " + _pooledCommands.Count); + pooledCommand.OdbcCommand.Prepare(); + } + + return pooledCommand; + } + + public void ReleaseCommand(PooledCommand pooledCommand) + { + _pooledCommands.TryAdd(pooledCommand.CommandText, pooledCommand); + } + } +} \ No newline at end of file diff --git a/frameworks/CSharp/appmpower/src/Db/PooledConnections.cs b/frameworks/CSharp/appmpower/src/Db/PooledConnections.cs new file mode 100644 index 00000000000..97bae3e9ed8 --- /dev/null +++ b/frameworks/CSharp/appmpower/src/Db/PooledConnections.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Concurrent; +using System.Data.Odbc; +using System.Threading.Tasks; + +namespace appMpower.Db +{ + public static class PooledConnections + { + private static int _maxLoops = 999; + private static byte _createdConnections = 0; + private static byte _maxConnections = Math.Min((byte)Environment.ProcessorCount, (byte)21); + private static ConcurrentStack _stack = new ConcurrentStack(); + + public static async Task GetConnection(string connectionString) + { + int i = 0; + PooledConnection pooledConnection; + + if (_createdConnections < _maxConnections) + { + pooledConnection = new PooledConnection(); + pooledConnection.OdbcConnection = new OdbcConnection(connectionString); + _createdConnections++; + pooledConnection.Number = _createdConnections; + pooledConnection.PooledCommands = new ConcurrentDictionary(); + //Console.WriteLine("opened connection number: " + pooledConnection.Number); + + return pooledConnection; + } + else + { + while (!_stack.TryPop(out pooledConnection) && i < _maxLoops) + { + if (i < 5) await Task.Delay(1); + else if (i < 10) await Task.Delay(2); + else if (i < 25) await Task.Delay(3); + else if (i < 50) await Task.Delay(4); + else if (i < 100) await Task.Delay(5); + else if (i < 500) await Task.Delay(10); + else await Task.Delay(20); + + i++; + //Console.WriteLine("waiting: " + i); + } + + if (i < _maxLoops) + { + //Console.WriteLine("opened connection number: " + pooledConnection.Number); + return pooledConnection; + } + else + { + throw new Exception("No connections are available"); + } + } + } + + public static void ReleaseConnection(PooledConnection pooledConnection) + { + PooledConnection stackedConnection = new PooledConnection(); + + stackedConnection.OdbcConnection = pooledConnection.OdbcConnection; + stackedConnection.Number = pooledConnection.Number; + stackedConnection.PooledCommands = pooledConnection.PooledCommands; + + _stack.Push(stackedConnection); + } + } +} \ No newline at end of file diff --git a/frameworks/CSharp/appmpower/src/Fortune.cs b/frameworks/CSharp/appmpower/src/Fortune.cs new file mode 100644 index 00000000000..4d1b99661ca --- /dev/null +++ b/frameworks/CSharp/appmpower/src/Fortune.cs @@ -0,0 +1,22 @@ +using System; + +namespace appMpower +{ + public readonly struct Fortune : IComparable, IComparable + { + public Fortune(int id, string message) + { + Id = id; + Message = message; + } + + public int Id { get; } + + public string Message { get; } + + public int CompareTo(object obj) => throw new InvalidOperationException("The non-generic CompareTo should not be used"); + + // Performance critical, using culture insensitive comparison + public int CompareTo(Fortune other) => string.CompareOrdinal(Message, other.Message); + } +} \ No newline at end of file diff --git a/frameworks/CSharp/appmpower/src/FortunesView.cs b/frameworks/CSharp/appmpower/src/FortunesView.cs new file mode 100644 index 00000000000..7b80aba0bef --- /dev/null +++ b/frameworks/CSharp/appmpower/src/FortunesView.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; +using System.IO.Pipelines; +using System.Web; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; +using PlatformBenchmarks; + +namespace appMpower +{ + public static class FortunesView + { + private readonly static KeyValuePair _headerServer = + new KeyValuePair("Server", "k"); + private readonly static KeyValuePair _headerContentType = + new KeyValuePair("Content-Type", "text/html; charset=UTF-8"); + + private readonly static AsciiString _fortunesTableStart = "Fortunes"; + private readonly static AsciiString _fortunesRowStart = ""; + private readonly static AsciiString _fortunesTableEnd = "
idmessage
"; + private readonly static AsciiString _fortunesColumn = ""; + private readonly static AsciiString _fortunesRowEnd = "
"; + + public static void Render(IHeaderDictionary headerDictionary, PipeWriter pipeWriter, List fortunes) + { + headerDictionary.Add(_headerServer); + headerDictionary.Add(_headerContentType); + + var bufferWriter = new BufferWriter(new WriterAdapter(pipeWriter), 1600); + + bufferWriter.Write(_fortunesTableStart); + + foreach (var fortune in fortunes) + { + bufferWriter.Write(_fortunesRowStart); + bufferWriter.WriteNumeric((uint)fortune.Id); + bufferWriter.Write(_fortunesColumn); + bufferWriter.WriteUtf8String(HttpUtility.HtmlEncode(fortune.Message)); + bufferWriter.Write(_fortunesRowEnd); + } + + bufferWriter.Write(_fortunesTableEnd); + headerDictionary.Add(new KeyValuePair("Content-Length", bufferWriter.Buffered.ToString())); + bufferWriter.Commit(); + } + } +} \ No newline at end of file diff --git a/frameworks/CSharp/appmpower/src/HttpApplication.cs b/frameworks/CSharp/appmpower/src/HttpApplication.cs new file mode 100644 index 00000000000..ac388f9d9b9 --- /dev/null +++ b/frameworks/CSharp/appmpower/src/HttpApplication.cs @@ -0,0 +1,95 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using appMpower.Kestrel; +using PlatformBenchmarks; + +namespace appMpower +{ + public class HttpApplication : IHttpApplication + { + private readonly static AsciiString _plainText = "Hello, World!"; + private readonly static JsonMessageSerializer _jsonMessageSerializer = new JsonMessageSerializer(); + private readonly static WorldSerializer _worldSerializer = new WorldSerializer(); + + public IFeatureCollection CreateContext(IFeatureCollection featureCollection) + { + return featureCollection; + } + + public async Task ProcessRequestAsync(IFeatureCollection featureCollection) + { + var request = featureCollection as IHttpRequestFeature; + var httpResponse = featureCollection as IHttpResponseFeature; + var httpResponseBody = featureCollection as IHttpResponseBodyFeature; + + PathString pathString = request.Path; + + if (pathString.HasValue) + { + int pathStringLength = pathString.Value.Length; + string pathStringStart = pathString.Value.Substring(1, 1); + + if (pathStringLength == 10 && pathStringStart == "p") + { + PlainText.Render(httpResponse.Headers, httpResponseBody.Writer, _plainText); + return; + } + else if (pathStringLength == 5 && pathStringStart == "j") + { + Json.RenderOne(httpResponse.Headers, httpResponseBody.Writer, new JsonMessage { message = "Hello, World!" }, _jsonMessageSerializer); + return; + } + else if (pathStringLength == 3 && pathStringStart == "d") + { + Json.RenderOne(httpResponse.Headers, httpResponseBody.Writer, await RawDb.LoadSingleQueryRow(), _worldSerializer); + return; + } + else if (pathStringLength == 8 && pathStringStart == "q") + { + int count = 1; + + if (!Int32.TryParse(request.QueryString.Substring(request.QueryString.LastIndexOf("=") + 1), out count) || count < 1) + { + count = 1; + } + else if (count > 500) + { + count = 500; + } + + //Json.RenderMany(httpResponse.Headers, httpResponseBody.Writer, await RawDb.LoadMultipleQueriesRows(count), _worldSerializer); + Json.RenderMany(httpResponse.Headers, httpResponseBody.Writer, await RawDb.ReadMultipleRows(count), _worldSerializer); + return; + } + else if (pathStringLength == 9 && pathStringStart == "f") + { + FortunesView.Render(httpResponse.Headers, httpResponseBody.Writer, await RawDb.LoadFortunesRows()); + return; + } + else if (pathStringLength == 8 && pathStringStart == "u") + { + int count = 1; + + if (!Int32.TryParse(request.QueryString.Substring(request.QueryString.LastIndexOf("=") + 1), out count) || count < 1) + { + count = 1; + } + else if (count > 500) + { + count = 500; + } + + Json.RenderMany(httpResponse.Headers, httpResponseBody.Writer, await RawDb.LoadMultipleUpdatesRows(count), _worldSerializer); + return; + } + } + } + + public void DisposeContext(IFeatureCollection featureCollection, Exception exception) + { + } + } +} \ No newline at end of file diff --git a/frameworks/CSharp/appmpower/src/JsonMessage.cs b/frameworks/CSharp/appmpower/src/JsonMessage.cs new file mode 100644 index 00000000000..24b78265baa --- /dev/null +++ b/frameworks/CSharp/appmpower/src/JsonMessage.cs @@ -0,0 +1,7 @@ +namespace appMpower +{ + public struct JsonMessage + { + public string message { get; set; } + } +} \ No newline at end of file diff --git a/frameworks/CSharp/appmpower/src/JsonMessageSerializer.cs b/frameworks/CSharp/appmpower/src/JsonMessageSerializer.cs new file mode 100644 index 00000000000..dca7f673368 --- /dev/null +++ b/frameworks/CSharp/appmpower/src/JsonMessageSerializer.cs @@ -0,0 +1,14 @@ +using System.Text.Json; + +namespace appMpower +{ + public class JsonMessageSerializer : Kestrel.IJsonSerializer + { + public void Serialize(Utf8JsonWriter utf8JsonWriter, JsonMessage jsonMessage) + { + utf8JsonWriter.WriteStartObject(); + utf8JsonWriter.WriteString("message", jsonMessage.message); + utf8JsonWriter.WriteEndObject(); + } + } +} diff --git a/frameworks/CSharp/appmpower/src/Kestrel/IJsonSerializer.cs b/frameworks/CSharp/appmpower/src/Kestrel/IJsonSerializer.cs new file mode 100644 index 00000000000..4f28a4cc538 --- /dev/null +++ b/frameworks/CSharp/appmpower/src/Kestrel/IJsonSerializer.cs @@ -0,0 +1,9 @@ +using System.Text.Json; + +namespace appMpower.Kestrel +{ + public interface IJsonSerializer + { + public void Serialize(Utf8JsonWriter utf8JsonWriter, T t); + } +} \ No newline at end of file diff --git a/frameworks/CSharp/appmpower/src/Kestrel/Json.cs b/frameworks/CSharp/appmpower/src/Kestrel/Json.cs new file mode 100644 index 00000000000..e4ea9b6cb49 --- /dev/null +++ b/frameworks/CSharp/appmpower/src/Kestrel/Json.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.IO.Pipelines; +using System.Text.Json; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; + +namespace appMpower.Kestrel +{ + public static class Json + { + private readonly static KeyValuePair _headerServer = + new KeyValuePair("Server", "k"); + private readonly static KeyValuePair _headerContentType = + new KeyValuePair("Content-Type", "application/json"); + + [ThreadStatic] + private static Utf8JsonWriter _utf8JsonWriter; + + public static JsonWriterOptions _jsonWriterOptions = new JsonWriterOptions + { + SkipValidation = true + }; + + public static void RenderOne(IHeaderDictionary headerDictionary, PipeWriter pipeWriter, T t, IJsonSerializer jsonSerializer) + { + headerDictionary.Add(_headerServer); + headerDictionary.Add(_headerContentType); + + Utf8JsonWriter utf8JsonWriter = _utf8JsonWriter ??= new Utf8JsonWriter(pipeWriter, new JsonWriterOptions { SkipValidation = true }); + utf8JsonWriter.Reset(pipeWriter); + + jsonSerializer.Serialize(utf8JsonWriter, t); + utf8JsonWriter.Flush(); + headerDictionary.Add(new KeyValuePair("Content-Length", ((uint)utf8JsonWriter.BytesCommitted).ToString())); + } + + public static void RenderMany(IHeaderDictionary headerDictionary, PipeWriter pipeWriter, T[] tArray, IJsonSerializer jsonSerializer) + { + headerDictionary.Add(_headerServer); + headerDictionary.Add(_headerContentType); + + Utf8JsonWriter utf8JsonWriter = _utf8JsonWriter ??= new Utf8JsonWriter(pipeWriter, new JsonWriterOptions { SkipValidation = true }); + utf8JsonWriter.Reset(pipeWriter); + + utf8JsonWriter.WriteStartArray(); + + foreach (var t in tArray) + { + jsonSerializer.Serialize(utf8JsonWriter, t); + } + + utf8JsonWriter.WriteEndArray(); + utf8JsonWriter.Flush(); + headerDictionary.Add(new KeyValuePair("Content-Length", ((uint)utf8JsonWriter.BytesCommitted).ToString())); + } + } +} \ No newline at end of file diff --git a/frameworks/CSharp/appmpower/src/Kestrel/PlainText.cs b/frameworks/CSharp/appmpower/src/Kestrel/PlainText.cs new file mode 100644 index 00000000000..def7bf9fcf2 --- /dev/null +++ b/frameworks/CSharp/appmpower/src/Kestrel/PlainText.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using System.IO.Pipelines; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; +using PlatformBenchmarks; + +namespace appMpower.Kestrel +{ + public static class PlainText + { + private readonly static KeyValuePair _headerServer = + new KeyValuePair("Server", new StringValues("k")); + private readonly static KeyValuePair _headerContentType = + new KeyValuePair("Content-Type", new StringValues("text/plain")); + + public static void Render(IHeaderDictionary headerDictionary, PipeWriter pipeWriter, AsciiString utf8String) + { + headerDictionary.Add(_headerServer); + headerDictionary.Add(_headerContentType); + headerDictionary.Add(new KeyValuePair("Content-Length", utf8String.Length.ToString())); + + var bufferWriter = new BufferWriter(new WriterAdapter(pipeWriter), 208); + bufferWriter.Write(utf8String); + bufferWriter.Commit(); + } + } +} \ No newline at end of file diff --git a/frameworks/CSharp/appmpower/src/Kestrel/ServiceProvider.cs b/frameworks/CSharp/appmpower/src/Kestrel/ServiceProvider.cs new file mode 100644 index 00000000000..513174e68b9 --- /dev/null +++ b/frameworks/CSharp/appmpower/src/Kestrel/ServiceProvider.cs @@ -0,0 +1,25 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; + +namespace appMpower.Kestrel +{ + public class ServiceProvider : ISupportRequiredService, IServiceProvider + { + public object GetRequiredService(Type serviceType) + { + return GetService(serviceType); + } + + public object GetService(Type serviceType) + { + if (serviceType == typeof(ILoggerFactory)) + { + return NullLoggerFactory.Instance; + } + + return null; + } + } +} \ No newline at end of file diff --git a/frameworks/CSharp/appmpower/src/Microsoft/AsciiString.cs b/frameworks/CSharp/appmpower/src/Microsoft/AsciiString.cs new file mode 100644 index 00000000000..d9ec3d94198 --- /dev/null +++ b/frameworks/CSharp/appmpower/src/Microsoft/AsciiString.cs @@ -0,0 +1,58 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Text; + +namespace PlatformBenchmarks +{ + public readonly struct AsciiString : IEquatable + { + private readonly byte[] _data; + + public AsciiString(string s) => _data = Encoding.ASCII.GetBytes(s); + + private AsciiString(byte[] b) => _data = b; + + public int Length => _data.Length; + + public ReadOnlySpan AsSpan() => _data; + + public static implicit operator ReadOnlySpan(AsciiString str) => str._data; + public static implicit operator byte[](AsciiString str) => str._data; + + public static implicit operator AsciiString(string str) => new AsciiString(str); + + public override string ToString() => Encoding.ASCII.GetString(_data); + public static explicit operator string(AsciiString str) => str.ToString(); + + public bool Equals(AsciiString other) => ReferenceEquals(_data, other._data) || SequenceEqual(_data, other._data); + private bool SequenceEqual(byte[] data1, byte[] data2) => new Span(data1).SequenceEqual(data2); + + public static bool operator ==(AsciiString a, AsciiString b) => a.Equals(b); + public static bool operator !=(AsciiString a, AsciiString b) => !a.Equals(b); + public override bool Equals(object other) => (other is AsciiString) && Equals((AsciiString)other); + + public static AsciiString operator +(AsciiString a, AsciiString b) + { + var result = new byte[a.Length + b.Length]; + a._data.CopyTo(result, 0); + b._data.CopyTo(result, a.Length); + return new AsciiString(result); + } + + public override int GetHashCode() + { + // Copied from x64 version of string.GetLegacyNonRandomizedHashCode() + // https://github.com/dotnet/coreclr/blob/master/src/mscorlib/src/System/String.Comparison.cs + var data = _data; + int hash1 = 5381; + int hash2 = hash1; + foreach (int b in data) + { + hash1 = ((hash1 << 5) + hash1) ^ b; + } + return hash1 + (hash2 * 1566083941); + } + } +} \ No newline at end of file diff --git a/frameworks/CSharp/appmpower/src/Microsoft/BatchUpdateString.cs b/frameworks/CSharp/appmpower/src/Microsoft/BatchUpdateString.cs new file mode 100644 index 00000000000..06721f99100 --- /dev/null +++ b/frameworks/CSharp/appmpower/src/Microsoft/BatchUpdateString.cs @@ -0,0 +1,65 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Linq; + +namespace PlatformBenchmarks +{ + internal class BatchUpdateString + { + private const int MaxBatch = 500; + + internal static readonly string[] Ids = Enumerable.Range(0, MaxBatch).Select(i => $"i{i}").ToArray(); + internal static readonly string[] Randoms = Enumerable.Range(0, MaxBatch).Select(i => $"r{i}").ToArray(); + internal static readonly string[] Jds = Enumerable.Range(0, MaxBatch).Select(i => $"j{i}").ToArray(); + + private static string[] _queries = new string[MaxBatch + 1]; + + public static string Query(int batchSize) + { + if (_queries[batchSize] != null) + { + return _queries[batchSize]; + } + + var lastIndex = batchSize - 1; + + var sb = StringBuilderCache.Acquire(); + + //if (DatabaseServer == DatabaseServer.PostgreSql) + //{ + //sb.Append("UPDATE world SET randomNumber = temp.randomNumber FROM (VALUES "); + //Enumerable.Range(0, lastIndex).ToList().ForEach(i => sb.Append($"(@Id_{i}, @Random_{i}), ")); + //sb.Append($"(@Id_{lastIndex}, @Random_{lastIndex}) ORDER BY 1) AS temp(id, randomNumber) WHERE temp.id = world.id"); + //} + //else + //{ + // Enumerable.Range(0, batchSize).ToList().ForEach(i => sb.Append($"UPDATE world SET randomnumber = @Random_{i} WHERE id = @Id_{i};")); + //} + + //sb.Append("UPDATE world SET randomNumber = CAST(temp.randomNumber AS INTEGER) FROM (VALUES "); + //Enumerable.Range(0, lastIndex).ToList().ForEach(i => sb.Append("(?, ?), ")); + //sb.Append("(?, ?) ORDER BY 1) AS temp(id, randomNumber) WHERE CAST(temp.id AS INTEGER) = world.id"); + + sb.Append("UPDATE world SET randomNumber = CASE id "); + + for (int i = 0; i < batchSize; i++) + { + sb.Append("WHEN ? THEN ? "); + } + + //Enumerable.Range(0, batchSize).ToList().ForEach(i => sb.Append("WHEN ? THEN ? ")); + sb.Append("ELSE randomnumber END WHERE id IN ("); + + for (int i = 0; i < lastIndex; i++) + { + sb.Append("?, "); + } + + //Enumerable.Range(0, lastIndex).ToList().ForEach(i => sb.Append("?, ")); + sb.Append("?)"); + + return _queries[batchSize] = StringBuilderCache.GetStringAndRelease(sb); + } + } +} \ No newline at end of file diff --git a/frameworks/CSharp/appmpower/src/Microsoft/BufferExtensions.cs b/frameworks/CSharp/appmpower/src/Microsoft/BufferExtensions.cs new file mode 100644 index 00000000000..bc5b7d26a72 --- /dev/null +++ b/frameworks/CSharp/appmpower/src/Microsoft/BufferExtensions.cs @@ -0,0 +1,63 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using System.Text; + +namespace PlatformBenchmarks +{ + // Same as KestrelHttpServer\src\Kestrel.Core\Internal\Http\PipelineExtensions.cs + // However methods accept T : struct, IBufferWriter rather than PipeWriter. + // This allows a struct wrapper to turn CountingBufferWriter into a non-shared generic, + // while still offering the WriteNumeric extension. + + public static class BufferExtensions + { + private const int _maxULongByteLength = 20; + + [ThreadStatic] + private static byte[] _numericBytesScratch; + + internal static void WriteUtf8String(ref this BufferWriter buffer, string text) + where T : struct, IBufferWriter + { + var byteCount = Encoding.UTF8.GetByteCount(text); + buffer.Ensure(byteCount); + byteCount = Encoding.UTF8.GetBytes(text.AsSpan(), buffer.Span); + buffer.Advance(byteCount); + } + [MethodImpl(MethodImplOptions.NoInlining)] + internal static void WriteNumericMultiWrite(ref this BufferWriter buffer, uint number) + where T : IBufferWriter + { + const byte AsciiDigitStart = (byte)'0'; + + var value = number; + var position = _maxULongByteLength; + var byteBuffer = NumericBytesScratch; + do + { + // Consider using Math.DivRem() if available + var quotient = value / 10; + byteBuffer[--position] = (byte)(AsciiDigitStart + (value - quotient * 10)); // 0x30 = '0' + value = quotient; + } + while (value != 0); + + var length = _maxULongByteLength - position; + buffer.Write(new ReadOnlySpan(byteBuffer, position, length)); + } + + private static byte[] NumericBytesScratch => _numericBytesScratch ?? CreateNumericBytesScratch(); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static byte[] CreateNumericBytesScratch() + { + var bytes = new byte[_maxULongByteLength]; + _numericBytesScratch = bytes; + return bytes; + } + } +} \ No newline at end of file diff --git a/frameworks/CSharp/appmpower/src/Microsoft/BufferWriter.cs b/frameworks/CSharp/appmpower/src/Microsoft/BufferWriter.cs new file mode 100644 index 00000000000..c3316771319 --- /dev/null +++ b/frameworks/CSharp/appmpower/src/Microsoft/BufferWriter.cs @@ -0,0 +1,143 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Buffers; +using System.Runtime.CompilerServices; + +namespace PlatformBenchmarks +{ + public ref struct BufferWriter where T : IBufferWriter + { + private T _output; + private Span _span; + private int _buffered; + + public BufferWriter(T output, int sizeHint) + { + _buffered = 0; + _output = output; + _span = output.GetSpan(sizeHint); + } + + public Span Span => _span; + + public T Output => _output; + + public int Buffered => _buffered; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Commit() + { + var buffered = _buffered; + if (buffered > 0) + { + _buffered = 0; + _output.Advance(buffered); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Advance(int count) + { + _buffered += count; + _span = _span.Slice(count); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(ReadOnlySpan source) + { + if (_span.Length >= source.Length) + { + source.CopyTo(_span); + Advance(source.Length); + } + else + { + WriteMultiBuffer(source); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Ensure(int count = 1) + { + if (_span.Length < count) + { + EnsureMore(count); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void EnsureMore(int count = 0) + { + if (_buffered > 0) + { + Commit(); + } + + _span = _output.GetSpan(count); + } + + private void WriteMultiBuffer(ReadOnlySpan source) + { + while (source.Length > 0) + { + if (_span.Length == 0) + { + EnsureMore(); + } + + var writable = Math.Min(source.Length, _span.Length); + source.Slice(0, writable).CopyTo(_span); + source = source.Slice(writable); + Advance(writable); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void WriteNumeric(uint number) + { + const byte AsciiDigitStart = (byte)'0'; + + var span = this.Span; + + // Fast path, try copying to the available memory directly + var advanceBy = 0; + if (span.Length >= 3) + { + if (number < 10) + { + span[0] = (byte)(number + AsciiDigitStart); + advanceBy = 1; + } + else if (number < 100) + { + var tens = (byte)((number * 205u) >> 11); // div10, valid to 1028 + + span[0] = (byte)(tens + AsciiDigitStart); + span[1] = (byte)(number - (tens * 10) + AsciiDigitStart); + advanceBy = 2; + } + else if (number < 1000) + { + var digit0 = (byte)((number * 41u) >> 12); // div100, valid to 1098 + var digits01 = (byte)((number * 205u) >> 11); // div10, valid to 1028 + + span[0] = (byte)(digit0 + AsciiDigitStart); + span[1] = (byte)(digits01 - (digit0 * 10) + AsciiDigitStart); + span[2] = (byte)(number - (digits01 * 10) + AsciiDigitStart); + advanceBy = 3; + } + } + + if (advanceBy > 0) + { + Advance(advanceBy); + } + else + { + BufferExtensions.WriteNumericMultiWrite(ref this, number); + } + } + } +} \ No newline at end of file diff --git a/frameworks/CSharp/appmpower/src/Microsoft/StringBuilderCache.cs b/frameworks/CSharp/appmpower/src/Microsoft/StringBuilderCache.cs new file mode 100644 index 00000000000..6f0b20b7278 --- /dev/null +++ b/frameworks/CSharp/appmpower/src/Microsoft/StringBuilderCache.cs @@ -0,0 +1,59 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Text; + +namespace PlatformBenchmarks +{ + internal static class StringBuilderCache + { + private const int DefaultCapacity = 1386; + private const int MaxBuilderSize = DefaultCapacity * 3; + + [ThreadStatic] + private static StringBuilder t_cachedInstance; + + /// Get a StringBuilder for the specified capacity. + /// If a StringBuilder of an appropriate size is cached, it will be returned and the cache emptied. + public static StringBuilder Acquire(int capacity = DefaultCapacity) + { + if (capacity <= MaxBuilderSize) + { + StringBuilder sb = t_cachedInstance; + if (capacity < DefaultCapacity) + { + capacity = DefaultCapacity; + } + + if (sb != null) + { + // Avoid stringbuilder block fragmentation by getting a new StringBuilder + // when the requested size is larger than the current capacity + if (capacity <= sb.Capacity) + { + t_cachedInstance = null; + sb.Clear(); + return sb; + } + } + } + return new StringBuilder(capacity); + } + + public static void Release(StringBuilder sb) + { + if (sb.Capacity <= MaxBuilderSize) + { + t_cachedInstance = sb; + } + } + + public static string GetStringAndRelease(StringBuilder sb) + { + string result = sb.ToString(); + Release(sb); + return result; + } + } +} \ No newline at end of file diff --git a/frameworks/CSharp/appmpower/src/Microsoft/WriterAdapter.cs b/frameworks/CSharp/appmpower/src/Microsoft/WriterAdapter.cs new file mode 100644 index 00000000000..f82cde34ec6 --- /dev/null +++ b/frameworks/CSharp/appmpower/src/Microsoft/WriterAdapter.cs @@ -0,0 +1,23 @@ +using System; +using System.Buffers; +using System.IO.Pipelines; + +namespace PlatformBenchmarks +{ + public struct WriterAdapter : IBufferWriter + { + public PipeWriter Writer; + + public WriterAdapter(PipeWriter writer) + => Writer = writer; + + public void Advance(int count) + => Writer.Advance(count); + + public Memory GetMemory(int sizeHint = 0) + => Writer.GetMemory(sizeHint); + + public Span GetSpan(int sizeHint = 0) + => Writer.GetSpan(sizeHint); + } +} \ No newline at end of file diff --git a/frameworks/CSharp/appmpower/src/Program.cs b/frameworks/CSharp/appmpower/src/Program.cs new file mode 100644 index 00000000000..5647ac9b787 --- /dev/null +++ b/frameworks/CSharp/appmpower/src/Program.cs @@ -0,0 +1,41 @@ +using System; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting.Server.Features; +using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; + +namespace appMpower +{ + class Program + { + static async Task Main(string[] args) + { + var socketTransportOptions = new SocketTransportOptions(); + var socketTransportFactory = new SocketTransportFactory(Options.Create(socketTransportOptions), NullLoggerFactory.Instance); + var kestrelServerOptions = new KestrelServerOptions(); + + kestrelServerOptions.Listen(IPAddress.Any, 8080); + kestrelServerOptions.AddServerHeader = false; + + using var kestrelServer = new KestrelServer(Options.Create(kestrelServerOptions), socketTransportFactory, NullLoggerFactory.Instance); + + await kestrelServer.StartAsync(new HttpApplication(), CancellationToken.None); + + Console.WriteLine("Listening on:"); + + foreach (var address in kestrelServer.Features.Get().Addresses) + { + Console.WriteLine(" - " + address); + } + + Console.WriteLine("Process CTRL+C to quit"); + var wh = new ManualResetEventSlim(); + Console.CancelKeyPress += (sender, e) => wh.Set(); + wh.Wait(); + } + } +} \ No newline at end of file diff --git a/frameworks/CSharp/appmpower/src/RawDb.cs b/frameworks/CSharp/appmpower/src/RawDb.cs new file mode 100644 index 00000000000..d237e78d63c --- /dev/null +++ b/frameworks/CSharp/appmpower/src/RawDb.cs @@ -0,0 +1,197 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Text; +using System.Threading.Tasks; +using appMpower.Db; + +namespace appMpower +{ + public static class RawDb + { + private const int MaxBatch = 500; + private static Random _random = new Random(); + private static string[] _queriesMultipleRows = new string[MaxBatch + 1]; + + public static async Task LoadSingleQueryRow() + { + using var pooledConnection = await PooledConnections.GetConnection(ConnectionStrings.OdbcConnection); + await pooledConnection.OpenAsync(); + + var (pooledCommand, _) = CreateReadCommand(pooledConnection); + + using (pooledCommand) + { + return await ReadSingleRow(pooledCommand); + } + } + + public static async Task LoadMultipleQueriesRows(int count) + { + var worlds = new World[count]; + + using var pooledConnection = await PooledConnections.GetConnection(ConnectionStrings.OdbcConnection); + await pooledConnection.OpenAsync(); + + var (pooledCommand, dbDataParameter) = CreateReadCommand(pooledConnection); + + using (pooledCommand) + { + for (int i = 0; i < count; i++) + { + worlds[i] = await ReadSingleRow(pooledCommand); + dbDataParameter.Value = _random.Next(1, 10001); + } + } + + return worlds; + } + + public static async Task> LoadFortunesRows() + { + var fortunes = new List(); + + using var pooledConnection = await PooledConnections.GetConnection(ConnectionStrings.OdbcConnection); + await pooledConnection.OpenAsync(); + + var pooledCommand = new PooledCommand("SELECT id, message FROM fortune", pooledConnection); + + using (pooledCommand) + { + using var dataReader = await pooledCommand.ExecuteReaderAsync(CommandBehavior.SingleResult); + + while (await dataReader.ReadAsync()) + { + fortunes.Add(new Fortune + ( + id: dataReader.GetInt32(0), + message: dataReader.GetString(1) + )); + } + } + + fortunes.Add(new Fortune(id: 0, message: "Additional fortune added at request time.")); + fortunes.Sort(); + + return fortunes; + } + + public static async Task LoadMultipleUpdatesRows(int count) + { + var worlds = new World[count]; + + using var pooledConnection = await PooledConnections.GetConnection(ConnectionStrings.OdbcConnection); + await pooledConnection.OpenAsync(); + + var (queryCommand, dbDataParameter) = CreateReadCommand(pooledConnection); + + using (queryCommand) + { + for (int i = 0; i < count; i++) + { + worlds[i] = await ReadSingleRow(queryCommand); + dbDataParameter.Value = _random.Next(1, 10001); + } + } + + var updateCommand = new PooledCommand(PlatformBenchmarks.BatchUpdateString.Query(count), pooledConnection); + + using (updateCommand) + { + var ids = PlatformBenchmarks.BatchUpdateString.Ids; + var randoms = PlatformBenchmarks.BatchUpdateString.Randoms; + var jds = PlatformBenchmarks.BatchUpdateString.Jds; + + for (int i = 0; i < count; i++) + { + var randomNumber = _random.Next(1, 10001); + + updateCommand.CreateParameter(ids[i], DbType.Int32, worlds[i].Id); + updateCommand.CreateParameter(randoms[i], DbType.Int32, randomNumber); + + worlds[i].RandomNumber = randomNumber; + } + + for (int i = 0; i < count; i++) + { + updateCommand.CreateParameter(jds[i], DbType.Int32, worlds[i].Id); + } + + await updateCommand.ExecuteNonQueryAsync(); + } + + return worlds; + } + + private static (PooledCommand pooledCommand, IDbDataParameter dbDataParameter) CreateReadCommand(PooledConnection pooledConnection) + { + var pooledCommand = new PooledCommand("SELECT id, randomnumber FROM world WHERE id =?", pooledConnection); + var dbDataParameter = pooledCommand.CreateParameter("@Id", DbType.Int32, _random.Next(1, 10001)); + + return (pooledCommand, dbDataParameter); + } + + private static async Task ReadSingleRow(PooledCommand pooledCommand) + { + using var dataReader = await pooledCommand.ExecuteReaderAsync(CommandBehavior.SingleRow); + await dataReader.ReadAsync(); + + return new World + { + Id = dataReader.GetInt32(0), + RandomNumber = dataReader.GetInt32(1) + }; + } + + public static async Task ReadMultipleRows(int count) + { + int j = 0; + var ids = PlatformBenchmarks.BatchUpdateString.Ids; + var worlds = new World[count]; + string queryString; + + if (_queriesMultipleRows[count] != null) + { + queryString = _queriesMultipleRows[count]; + } + else + { + var stringBuilder = PlatformBenchmarks.StringBuilderCache.Acquire(); + + for (int i = 0; i < count; i++) + { + stringBuilder.Append("SELECT id, randomnumber FROM world WHERE id =?;"); + } + + queryString = _queriesMultipleRows[count] = PlatformBenchmarks.StringBuilderCache.GetStringAndRelease(stringBuilder); + } + + using var pooledConnection = await PooledConnections.GetConnection(ConnectionStrings.OdbcConnection); + await pooledConnection.OpenAsync(); + + using var pooledCommand = new PooledCommand(queryString, pooledConnection); + + for (int i = 0; i < count; i++) + { + pooledCommand.CreateParameter(ids[i], DbType.Int32, _random.Next(1, 10001)); + } + + using var dataReader = await pooledCommand.ExecuteReaderAsync(CommandBehavior.Default); + + do + { + await dataReader.ReadAsync(); + + worlds[j] = new World + { + Id = dataReader.GetInt32(0), + RandomNumber = dataReader.GetInt32(1) + }; + + j++; + } while (await dataReader.NextResultAsync()); + + return worlds; + } + } +} \ No newline at end of file diff --git a/frameworks/CSharp/appmpower/src/World.cs b/frameworks/CSharp/appmpower/src/World.cs new file mode 100644 index 00000000000..cfec08b1f9c --- /dev/null +++ b/frameworks/CSharp/appmpower/src/World.cs @@ -0,0 +1,8 @@ +namespace appMpower +{ + public struct World + { + public int Id { get; set; } + public int RandomNumber { get; set; } + } +} \ No newline at end of file diff --git a/frameworks/CSharp/appmpower/src/WorldSerializer.cs b/frameworks/CSharp/appmpower/src/WorldSerializer.cs new file mode 100644 index 00000000000..6150c5560b7 --- /dev/null +++ b/frameworks/CSharp/appmpower/src/WorldSerializer.cs @@ -0,0 +1,15 @@ +using System.Text.Json; + +namespace appMpower +{ + public class WorldSerializer : Kestrel.IJsonSerializer + { + public void Serialize(Utf8JsonWriter utf8JsonWriter, World world) + { + utf8JsonWriter.WriteStartObject(); + utf8JsonWriter.WriteNumber("id", world.Id); + utf8JsonWriter.WriteNumber("randomNumber", world.RandomNumber); + utf8JsonWriter.WriteEndObject(); + } + } +} diff --git a/frameworks/CSharp/appmpower/src/appMpower.csproj b/frameworks/CSharp/appmpower/src/appMpower.csproj new file mode 100644 index 00000000000..ed8c03334a8 --- /dev/null +++ b/frameworks/CSharp/appmpower/src/appMpower.csproj @@ -0,0 +1,27 @@ + + + + net5.0 + Exe + true + + link + + linux-x64 + true + true + Speed + true + false + + true + false + false + + + + + + + + \ No newline at end of file diff --git a/frameworks/CSharp/appmpower/src/nuget.config b/frameworks/CSharp/appmpower/src/nuget.config new file mode 100644 index 00000000000..a452a6a1207 --- /dev/null +++ b/frameworks/CSharp/appmpower/src/nuget.config @@ -0,0 +1,9 @@ + + + + + + + + +