Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
256 changes: 95 additions & 161 deletions frameworks/sisk/Program.cs
Original file line number Diff line number Diff line change
@@ -1,113 +1,53 @@
using System.Diagnostics;
using System.Net.Http.Json;

using System.Text.Json;
using sisk;
using Npgsql;
using sisk;
using Sisk.Cadente.CoreEngine;
using Sisk.Core.Http;
using Sisk.Core.Http.FileSystem;
using Sisk.Core.Routing;

var server = HttpServer.CreateBuilder()
.UseEngine<CadenteHttpServerEngine>()
.UseListeningPort(new ListeningPort(false, "0.0.0.0", 8080))
.UseMinimalConfiguration();

// Pre-load static files with pre-compressed variants
var staticMimeTypes = new Dictionary<string, string>
{
[".css"] = "text/css", [".js"] = "application/javascript", [".html"] = "text/html",
[".woff2"] = "font/woff2", [".svg"] = "image/svg+xml", [".webp"] = "image/webp", [".json"] = "application/json",
};
var staticCache = new Dictionary<string, (byte[] data, byte[]? br, byte[]? gz, string contentType)>();
if (Directory.Exists("/data/static"))
{
foreach (var file in Directory.GetFiles("/data/static"))
{
var name = Path.GetFileName(file);
if (name.EndsWith(".br") || name.EndsWith(".gz")) continue;
var ext = Path.GetExtension(name);
var ct = staticMimeTypes.GetValueOrDefault(ext, "application/octet-stream");
var brPath = file + ".br";
var gzPath = file + ".gz";
staticCache[name] = (
File.ReadAllBytes(file),
File.Exists(brPath) ? File.ReadAllBytes(brPath) : null,
File.Exists(gzPath) ? File.ReadAllBytes(gzPath) : null,
ct
);
}
}

Router router = new Router();

router.MapGet("/static/<filename>", r =>
{
var filename = r.RouteParameters["filename"].ToString();
if (!staticCache.TryGetValue(filename, out var sf))
return new HttpResponse(404);
var ae = r.Headers.AcceptEncoding ?? "";
byte[] body;
string? encoding = null;
if (sf.br != null && ae.Contains("br"))
{
body = sf.br;
encoding = "br";
}
else if (sf.gz != null && ae.Contains("gzip"))
{
body = sf.gz;
encoding = "gzip";
}
else
{
body = sf.data;
}
var resp = new HttpResponse(200);
resp.Content = new ByteArrayContent(body);
resp.Headers.Add("Content-Type", sf.contentType);
if (encoding != null) resp.Headers.Add("Content-Encoding", encoding);
return resp;
});

router.MapGet("/baseline11", r => new HttpResponse(Sum(r)));
router.MapPost("/baseline11", r => new HttpResponse(Sum(r)));
var server = HttpServer.CreateBuilder ()
.UseEngine<CadenteHttpServerEngine> ()
.UseListeningPort ( new ListeningPort ( false, "0.0.0.0", 8080 ) )
.UseMinimalConfiguration ()
.UseConfiguration ( config => {
config.EnableAutomaticResponseCompression = true;
} );

router.MapGet("/baseline2", r => new HttpResponse(Sum(r)));
Router router = new Router ();

router.MapGet("/pipeline", r => new HttpResponse("ok"));
var staticRoute = HttpFileServer.CreateServingRoute ( "/static", new HttpFileServerHandler () {
RootDirectoryPath = "/data/static",
AllowDirectoryListing = false
} );

router.MapPost("/upload", r =>
{
var buffer = new byte[8192];
router.SetRoute ( staticRoute );

var body = r.GetRequestStream();
router.MapGet ( "/baseline11", r => new HttpResponse ( Sum ( r ) ) );
router.MapPost ( "/baseline11", r => new HttpResponse ( Sum ( r ) ) );

var read = 0;
router.MapGet ( "/baseline2", r => new HttpResponse ( Sum ( r ) ) );

long total = 0;
router.MapGet ( "/pipeline", r => new HttpResponse ( "ok" ) );

while ((read = body.Read(buffer, 0, buffer.Length)) > 0)
{
total += read;
}
router.MapPost ( "/upload", r => {
var body = r.GetBodyContents ();
return new HttpResponse ( body.Length.ToString () );
} );

return new HttpResponse(total.ToString());
});
var datasetItems = LoadItems ();

var datasetItems = LoadItems();

router.MapGet("/json/<count>", r =>
{
int count = Math.Clamp(int.Parse(r.RouteParameters["count"].ToString()), 0, datasetItems!.Count);
router.MapGet ( "/json/<count>", r => {
int count = Math.Clamp ( int.Parse ( r.RouteParameters [ "count" ].GetString () ), 0, datasetItems!.Count );
int m = 1;
if (r.Query.TryGetValue("m", out var mStr)) { int.TryParse(mStr, out m); if (m == 0) m = 1; }
var processed = new ProcessedItem[count];

for (int i = 0; i < count; i++)
{
var d = datasetItems[i];
processed[i] = new ProcessedItem
{
if (r.Query.TryGetValue ( "m", out var mStr )) { int.TryParse ( mStr, out m ); if (m == 0) m = 1; }
var processed = new ProcessedItem [ count ];

for (int i = 0; i < count; i++) {
var d = datasetItems [ i ];
processed [ i ] = new ProcessedItem {
Id = d.Id,
Name = d.Name,
Category = d.Category,
Expand All @@ -120,99 +60,93 @@
};
}

return new HttpResponse
{
Content = JsonContent.Create(new ListWithCount<ProcessedItem>(processed.ToList()))
return new HttpResponse {
Content = JsonContent.Create ( new ListWithCount<ProcessedItem> ( processed.ToList () ) )
};
});

var pgDataSource = OpenPgPool();

router.MapGet("/async-db", async (HttpRequest request) =>
{
var min = request.Query.TryGetValue("min", out var vmin) ? vmin.GetInteger() : 10;
var max = request.Query.TryGetValue("max", out var vmax) ? vmax.GetInteger() : 50;
var limit = request.Query.TryGetValue("limit", out var vlim) ? Math.Clamp(vlim.GetInteger(), 1, 50) : 50;

await using var cmd = pgDataSource.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<object>();

while (await reader.ReadAsync())
{
items.Add(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<List<string>>(reader.GetString(6)),
rating = new { score = reader.GetInt32(7), count = reader.GetInt32(8) },
});
} );

var pgDataSource = OpenPgPool ();

router.MapGet ( "/async-db", async ( HttpRequest request ) => {
var min = request.Query.TryGetValue ( "min", out var vmin ) ? vmin.GetInteger () : 10;
var max = request.Query.TryGetValue ( "max", out var vmax ) ? vmax.GetInteger () : 50;
var limit = request.Query.TryGetValue ( "limit", out var vlim ) ? Math.Clamp ( vlim.GetInteger (), 1, 50 ) : 50;

Debug.Assert ( pgDataSource != null, "PostgreSQL data source is not available. Please set the DATABASE_URL environment variable." );

await using var cmd = pgDataSource.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<object> ();

while (await reader.ReadAsync ()) {
items.Add ( 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<List<string>> ( reader.GetString ( 6 ) ),
rating = new { score = reader.GetInt32 ( 7 ), count = reader.GetInt32 ( 8 ) },
} );
}

return new HttpResponse
{
Content = JsonContent.Create(new ListWithCount<object>(items))
return new HttpResponse {
Content = JsonContent.Create ( new ListWithCount<object> ( items ) )
};
});
} );

await server.UseRouter(router).Build().StartAsync();
await server.UseRouter ( router ).Build ().StartAsync ();

return;

static string Sum(HttpRequest request)
{
var a = request.Query["a"].MaybeNullOrEmpty()?.GetInteger() ?? 0;
var b = request.Query["b"].MaybeNullOrEmpty()?.GetInteger() ?? 0;
static string Sum ( HttpRequest request ) {
var a = request.Query [ "a" ].MaybeNullOrEmpty ()?.GetInteger () ?? 0;
var b = request.Query [ "b" ].MaybeNullOrEmpty ()?.GetInteger () ?? 0;

var c = 0;

if (request.Method == HttpMethod.Post)
{
c = int.Parse(request.Body);
if (request.Method == HttpMethod.Post) {
c = int.Parse ( request.Body );
}

return (a + b + c).ToString();
return (a + b + c).ToString ();
}

static List<DatasetItem>? LoadItems()
{
var jsonOptions = new JsonSerializerOptions
{
static List<DatasetItem>? LoadItems () {
var jsonOptions = new JsonSerializerOptions {
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};

var datasetPath = Environment.GetEnvironmentVariable("DATASET_PATH") ?? "/data/dataset.json";
var datasetPath = Environment.GetEnvironmentVariable ( "DATASET_PATH" ) ?? "/data/dataset.json";

if (File.Exists(datasetPath))
{
return JsonSerializer.Deserialize<List<DatasetItem>>(File.ReadAllText(datasetPath), jsonOptions);
if (File.Exists ( datasetPath )) {
return JsonSerializer.Deserialize<List<DatasetItem>> ( File.ReadAllText ( datasetPath ), jsonOptions );
}

return null;
}

static NpgsqlDataSource? OpenPgPool()
{
var dbUrl = Environment.GetEnvironmentVariable("DATABASE_URL");
if (string.IsNullOrEmpty(dbUrl)) return null;
try
{
var uri = new Uri(dbUrl);
var userInfo = uri.UserInfo.Split(':');
var connStr = $"Host={uri.Host};Port={uri.Port};Username={userInfo[0]};Password={userInfo[1]};Database={uri.AbsolutePath.TrimStart('/')};Maximum Pool Size=256;Minimum Pool Size=64;Multiplexing=true;No Reset On Close=true;Max Auto Prepare=4;Auto Prepare Min Usages=1";
var builder = new NpgsqlDataSourceBuilder(connStr);
return builder.Build();
static NpgsqlDataSource? OpenPgPool () {
var dbUrl = Environment.GetEnvironmentVariable ( "DATABASE_URL" );
if (string.IsNullOrEmpty ( dbUrl ))
return null;
try {
var uri = new Uri ( dbUrl );
var userInfo = uri.UserInfo.Split ( ':' );
var connStr = $"Host={uri.Host};Port={uri.Port};Username={userInfo [ 0 ]};Password={userInfo [ 1 ]};Database={uri.AbsolutePath.TrimStart ( '/' )};Maximum Pool Size=256;Minimum Pool Size=64;Multiplexing=true;No Reset On Close=true;Max Auto Prepare=4;Auto Prepare Min Usages=1";
var builder = new NpgsqlDataSourceBuilder ( connStr );
return builder.Build ();
}
catch {
return null;
}
catch { return null; }
}

24 changes: 24 additions & 0 deletions frameworks/sisk/sisk.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.2.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "sisk", "sisk.csproj", "{C0CA2CFC-1FB1-7DB1-1D65-1867C8466439}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C0CA2CFC-1FB1-7DB1-1D65-1867C8466439}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C0CA2CFC-1FB1-7DB1-1D65-1867C8466439}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C0CA2CFC-1FB1-7DB1-1D65-1867C8466439}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C0CA2CFC-1FB1-7DB1-1D65-1867C8466439}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0131EE85-9736-4BCD-950D-D09A342D540E}
EndGlobalSection
EndGlobal
26 changes: 26 additions & 0 deletions site/data/api-16-1024.json
Original file line number Diff line number Diff line change
Expand Up @@ -874,6 +874,32 @@
"tpl_static": 0,
"tpl_async_db": 163326
},
{
"framework": "sisk",
"language": "C#",
"rps": 82086,
"avg_latency": "12.18ms",
"p99_latency": "54.00ms",
"cpu": "1477.1%",
"memory": "240MiB",
"connections": 1024,
"threads": 64,
"duration": "5s",
"pipeline": 1,
"bandwidth": "414.81MB/s",
"input_bw": "4.62MB/s",
"reconnects": 246252,
"status_2xx": 1231297,
"status_3xx": 0,
"status_4xx": 0,
"status_5xx": 0,
"tpl_baseline": 461622,
"tpl_json": 461667,
"tpl_db": 0,
"tpl_upload": 0,
"tpl_static": 0,
"tpl_async_db": 308008
},
{
"framework": "SlimeWeb",
"language": "Python",
Expand Down
26 changes: 26 additions & 0 deletions site/data/api-4-256.json
Original file line number Diff line number Diff line change
Expand Up @@ -874,6 +874,32 @@
"tpl_static": 0,
"tpl_async_db": 65218
},
{
"framework": "sisk",
"language": "C#",
"rps": 33914,
"avg_latency": "6.92ms",
"p99_latency": "28.00ms",
"cpu": "333.9%",
"memory": "154MiB",
"connections": 256,
"threads": 64,
"duration": "5s",
"pipeline": 1,
"bandwidth": "171.31MB/s",
"input_bw": "1.91MB/s",
"reconnects": 101709,
"status_2xx": 508720,
"status_3xx": 0,
"status_4xx": 0,
"status_5xx": 0,
"tpl_baseline": 190879,
"tpl_json": 190927,
"tpl_db": 0,
"tpl_upload": 0,
"tpl_static": 0,
"tpl_async_db": 126914
},
{
"framework": "SlimeWeb",
"language": "Python",
Expand Down
Loading