2
0
mirror of https://github.com/esiur/esiur-dotnet.git synced 2026-04-29 06:48:41 +00:00
This commit is contained in:
2026-04-04 23:54:59 +03:00
parent 5b0fba89a4
commit 44983d7784
17 changed files with 181 additions and 66 deletions
+66 -19
View File
@@ -10,6 +10,7 @@ using Esiur.Resource;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Syntax;
using Org.BouncyCastle.Asn1.Cms;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
@@ -75,30 +76,71 @@ namespace Esiur.Proxy
{ {
try try
{ {
var code = @$"using Esiur.Resource; var code = new StringBuilder();
using Esiur.Core;
#nullable enable var ns = ci.ClassSymbol.ContainingNamespace.ToDisplayString();
if (ns == null || ns == "<global namespace>")
{
namespace {ci.ClassSymbol.ContainingNamespace.ToDisplayString()} {{ }
";
code.AppendLine("using Esiur.Resource;");
code.AppendLine("using Esiur.Core;");
code.AppendLine("#nullable enable");
if (!(ns == null || ns == "<global namespace>"))
{
code.AppendLine($"namespace {ns};");
}
code.AppendLine();
code.AppendLine($"// <auto-generated> DO NOT EDIT THIS FILE! </auto-generated>");
code.AppendLine($"// This file was generated by {nameof(ResourceGenerator)} based on the presence of [Resource] and [Export] attributes in {ci.ClassSymbol.Name}.cs");
if (IsInterfaceImplemented(ci, classes)) if (IsInterfaceImplemented(ci, classes))
code += $"public partial class {ci.Name} {{\r\n"; {
code.AppendLine($"public partial class {ci.Name}");
code.AppendLine("{");
}
else else
{ {
code += code.AppendLine($"public partial class {ci.Name} : IResource");
$@" public partial class {ci.Name} : IResource {{ code.AppendLine("{");
public virtual Instance? Instance {{ get; set; }} code.AppendLine("public virtual Instance? Instance { get; set; }");
public virtual event DestroyedEvent? OnDestroy; code.AppendLine("public virtual event DestroyedEvent? OnDestroy;");
code.AppendLine("public virtual void Destroy() { OnDestroy?.Invoke(this); }");
public virtual void Destroy() {{ OnDestroy?.Invoke(this); }}
";
if (!ci.HasTrigger) if (!ci.HasTrigger)
code += "\tpublic virtual AsyncReply<bool> Trigger(ResourceTrigger trigger) => new AsyncReply<bool>(true);\r\n\r\n"; {
code.AppendLine("public virtual AsyncReply<bool> Trigger(ResourceTrigger trigger) => new AsyncReply<bool>(true);");
}
} }
// var code = @$"using Esiur.Resource;
//using Esiur.Core;
//#nullable enable
//namespace {ci.ClassSymbol.ContainingNamespace.ToDisplayString()} {{
//";
// if (IsInterfaceImplemented(ci, classes))
// code += $"public partial class {ci.Name} {{\r\n";
// else
// {
// code +=
//$@" public partial class {ci.Name} : IResource {{
// public virtual Instance? Instance {{ get; set; }}
// public virtual event DestroyedEvent? OnDestroy;
// public virtual void Destroy() {{ OnDestroy?.Invoke(this); }}
//";
// if (!ci.HasTrigger)
// code += "\tpublic virtual AsyncReply<bool> Trigger(ResourceTrigger trigger) => new AsyncReply<bool>(true);\r\n\r\n";
// }
foreach (var f in ci.Fields) foreach (var f in ci.Fields)
{ {
var givenName = f.GetAttributes().FirstOrDefault(x => x.AttributeClass?.Name == "ExportAttribute")?.ConstructorArguments.FirstOrDefault().Value as string; var givenName = f.GetAttributes().FirstOrDefault(x => x.AttributeClass?.Name == "ExportAttribute")?.ConstructorArguments.FirstOrDefault().Value as string;
@@ -106,21 +148,26 @@ $@" public partial class {ci.Name} : IResource {{
var fn = f.Name; var fn = f.Name;
var pn = string.IsNullOrEmpty(givenName) ? SuggestExportName(fn) : givenName; var pn = string.IsNullOrEmpty(givenName) ? SuggestExportName(fn) : givenName;
var attrs = string.Join("\r\n\t", f.GetAttributes().Select(x => FormatAttribute(x))); var attrs = f.GetAttributes().Select(x => FormatAttribute(x));
foreach(var attr in attrs)
code.AppendLine($"\t{attr}");
if (f.Type.Name.StartsWith("ResourceEventHandler") || f.Type.Name.StartsWith("CustomResourceEventHandler")) if (f.Type.Name.StartsWith("ResourceEventHandler") || f.Type.Name.StartsWith("CustomResourceEventHandler"))
{ {
code += $"\t{attrs}\r\n\t public event {f.Type} {pn};\r\n"; code.AppendLine($"public event {f.Type} {pn};");
} }
else else
{ {
code += $"\t{attrs}\r\n\t public {f.Type} {pn} {{ \r\n\t\t get => {fn}; \r\n\t\t set {{ \r\n\t\t this.{fn} = value; \r\n\t\t Instance?.Modified(); \r\n\t\t}}\r\n\t}}\r\n"; code.AppendLine($"\t{attrs}\r\n\t public {f.Type} {pn} {{ \r\n\t\t get => {fn}; \r\n\t\t set {{ \r\n\t\t this.{fn} = value; \r\n\t\t Instance?.Modified(); \r\n\t\t}}\r\n\t}}\r\n");
} }
} }
code += "}}\r\n"; code.AppendLine("}\r\n");
spc.AddSource(ci.Name + ".g.cs", code); spc.AddSource(ci.Name + ".g.cs", code.ToString());
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -7,4 +7,8 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\Libraries\Esiur\Esiur.csproj" OutputItemType="Analyzer" />
</ItemGroup>
</Project> </Project>
@@ -19,7 +19,7 @@
using Esiur.Resource; using Esiur.Resource;
using Esiur.Stores; using Esiur.Stores;
using Esiur.Net.IIP; using Esiur.Protocol;
using System.Diagnostics; using System.Diagnostics;
var mode = GetArg(args, "--mode", "both"); var mode = GetArg(args, "--mode", "both");
@@ -29,28 +29,30 @@ var concurrent = int.Parse(GetArg(args, "--concurrent", "50"));
var resources = int.Parse(GetArg(args, "--resources", "200")); var resources = int.Parse(GetArg(args, "--resources", "200"));
var timeoutMs = int.Parse(GetArg(args, "--timeout", "10000")); var timeoutMs = int.Parse(GetArg(args, "--timeout", "10000"));
var rounds = int.Parse(GetArg(args, "--rounds", "5")); var rounds = int.Parse(GetArg(args, "--rounds", "5"));
var wh = new Warehouse();
// ---------------------------------------------------------------- // ----------------------------------------------------------------
// SERVER SIDE // SERVER SIDE
// ---------------------------------------------------------------- // ----------------------------------------------------------------
if (mode == "server" || mode == "both") if (mode == "server" || mode == "both")
{ {
await Warehouse.Put("sys", new MemoryStore());
await Warehouse.Put("sys/server", new DistributedServer() { Port = (ushort)port }); await wh.Put("sys", new MemoryStore());
await wh.Put("sys/server", new EpServer() { Port = (ushort)port });
for (int i = 0; i < resources; i++) for (int i = 0; i < resources; i++)
{ {
await Warehouse.Put($"sys/sensor_{i}", new SensorResource { SensorId = i, Value = i }); await wh.Put($"sys/sensor_{i}", new SensorResource { SensorId = i, Value = i });
} }
await Warehouse.Open(); await wh.Open();
Console.WriteLine($"[Server-T3] Ready: {resources} resources on port {port}"); Console.WriteLine($"[Server-T3] Ready: {resources} resources on port {port}");
if (mode == "server") if (mode == "server")
{ {
Console.WriteLine("Press ENTER to stop."); Console.WriteLine("Press ENTER to stop.");
Console.ReadLine(); Console.ReadLine();
await Warehouse.Close(); await wh.Close();
return; return;
} }
@@ -88,7 +90,7 @@ for (int round = 0; round < rounds; round++)
using var cts = new CancellationTokenSource(timeoutMs); using var cts = new CancellationTokenSource(timeoutMs);
try try
{ {
var proxy = await Warehouse.Get<IResource>( var proxy = await wh.Get<IResource>(
$"iip://{host}:{port}/sys/sensor_{resourceIdx}"); $"iip://{host}:{port}/sys/sensor_{resourceIdx}");
sw.Stop(); sw.Stop();
@@ -160,7 +162,7 @@ await File.WriteAllTextAsync("test3_concurrent_attach.csv", csv);
Console.WriteLine("\n[Client-T3] Results written to test3_concurrent_attach.csv"); Console.WriteLine("\n[Client-T3] Results written to test3_concurrent_attach.csv");
if (mode == "both") if (mode == "both")
await Warehouse.Close(); await wh.Close();
// ---------------------------------------------------------------- // ----------------------------------------------------------------
@@ -0,0 +1,15 @@
using Esiur.Resource;
/// <summary>
/// Shared observable sensor resource used across all scalability tests.
/// Property changes via Value setter are automatically propagated
/// to all attached remote peers by the Esiur runtime.
/// </summary>
[Resource]
public partial class SensorResource : Resource
{
public int SensorId { get; set; }
[Export]
public double value;
}
@@ -7,4 +7,8 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\Libraries\Esiur\Esiur.csproj" OutputItemType="Analyzer" />
</ItemGroup>
</Project> </Project>
@@ -17,9 +17,9 @@
// Usage (client only): dotnet run -- --mode client --host 127.0.0.1 --concurrent 50 --resources 200 // Usage (client only): dotnet run -- --mode client --host 127.0.0.1 --concurrent 50 --resources 200
// ============================================================ // ============================================================
using Esiur.Protocol;
using Esiur.Resource; using Esiur.Resource;
using Esiur.Stores; using Esiur.Stores;
using Esiur.Net.IIP;
using System.Diagnostics; using System.Diagnostics;
var mode = GetArg(args, "--mode", "both"); var mode = GetArg(args, "--mode", "both");
@@ -30,27 +30,28 @@ var resources = int.Parse(GetArg(args, "--resources", "200"));
var timeoutMs = int.Parse(GetArg(args, "--timeout", "10000")); var timeoutMs = int.Parse(GetArg(args, "--timeout", "10000"));
var rounds = int.Parse(GetArg(args, "--rounds", "5")); var rounds = int.Parse(GetArg(args, "--rounds", "5"));
var wh = new Warehouse();
// ---------------------------------------------------------------- // ----------------------------------------------------------------
// SERVER SIDE // SERVER SIDE
// ---------------------------------------------------------------- // ----------------------------------------------------------------
if (mode == "server" || mode == "both") if (mode == "server" || mode == "both")
{ {
await Warehouse.Put("sys", new MemoryStore()); await wh.Put("sys", new MemoryStore());
await Warehouse.Put("sys/server", new DistributedServer() { Port = (ushort)port }); await wh.Put("sys/server", new EpServer() { Port = (ushort)port });
for (int i = 0; i < resources; i++) for (int i = 0; i < resources; i++)
{ {
await Warehouse.Put($"sys/sensor_{i}", new SensorResource { SensorId = i, Value = i }); await wh.Put($"sys/sensor_{i}", new SensorResource { SensorId = i, Value = i });
} }
await Warehouse.Open(); await wh.Open();
Console.WriteLine($"[Server-T3] Ready: {resources} resources on port {port}"); Console.WriteLine($"[Server-T3] Ready: {resources} resources on port {port}");
if (mode == "server") if (mode == "server")
{ {
Console.WriteLine("Press ENTER to stop."); Console.WriteLine("Press ENTER to stop.");
Console.ReadLine(); Console.ReadLine();
await Warehouse.Close(); await wh.Close();
return; return;
} }
@@ -88,7 +89,7 @@ for (int round = 0; round < rounds; round++)
using var cts = new CancellationTokenSource(timeoutMs); using var cts = new CancellationTokenSource(timeoutMs);
try try
{ {
var proxy = await Warehouse.Get<IResource>( var proxy = await wh.Get<IResource>(
$"iip://{host}:{port}/sys/sensor_{resourceIdx}"); $"iip://{host}:{port}/sys/sensor_{resourceIdx}");
sw.Stop(); sw.Stop();
@@ -160,7 +161,7 @@ await File.WriteAllTextAsync("test3_concurrent_attach.csv", csv);
Console.WriteLine("\n[Client-T3] Results written to test3_concurrent_attach.csv"); Console.WriteLine("\n[Client-T3] Results written to test3_concurrent_attach.csv");
if (mode == "both") if (mode == "both")
await Warehouse.Close(); await wh.Close();
// ---------------------------------------------------------------- // ----------------------------------------------------------------
@@ -0,0 +1,15 @@
using Esiur.Resource;
/// <summary>
/// Shared observable sensor resource used across all scalability tests.
/// Property changes via Value setter are automatically propagated
/// to all attached remote peers by the Esiur runtime.
/// </summary>
[Resource]
public partial class SensorResource : Resource
{
public int SensorId { get; set; }
[Export]
public double value;
}
@@ -7,4 +7,8 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\Libraries\Esiur\Esiur.csproj" OutputItemType="Analyzer"/>
</ItemGroup>
</Project> </Project>
@@ -32,11 +32,13 @@ var latencyLock = new object();
var proxies = new dynamic[resourceCount]; var proxies = new dynamic[resourceCount];
var sw = Stopwatch.StartNew(); var sw = Stopwatch.StartNew();
var wh = new Warehouse();
try try
{ {
for (int i = 0; i < resourceCount; i++) for (int i = 0; i < resourceCount; i++)
{ {
proxies[i] = await Warehouse.Get<IResource>($"iip://{host}:{port}/sys/sensor_{i}"); proxies[i] = await wh.Get<IResource>($"iip://{host}:{port}/sys/sensor_{i}");
// Subscribe to property change notifications via the Esiur event model // Subscribe to property change notifications via the Esiur event model
double lastValue = (double)proxies[i].Value; double lastValue = (double)proxies[i].Value;
@@ -7,4 +7,8 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\Libraries\Esiur\Esiur.csproj" OutputItemType="Analyzer"/>
</ItemGroup>
</Project> </Project>
@@ -9,8 +9,8 @@
using Esiur.Resource; using Esiur.Resource;
using Esiur.Stores; using Esiur.Stores;
using Esiur.Net.IIP; using System.Diagnostics;
using System.Diagnostics; using Esiur.Protocol;
var resourceCount = int.Parse(GetArg(args, "--resources", "100")); var resourceCount = int.Parse(GetArg(args, "--resources", "100"));
var intervalMs = int.Parse(GetArg(args, "--interval", "50")); var intervalMs = int.Parse(GetArg(args, "--interval", "50"));
@@ -18,19 +18,20 @@ var port = int.Parse(GetArg(args, "--port", "10900"));
Console.WriteLine($"[Server] resources={resourceCount} interval={intervalMs}ms port={port}"); Console.WriteLine($"[Server] resources={resourceCount} interval={intervalMs}ms port={port}");
var wh = new Warehouse();
// --- Warehouse setup ------------------------------------------------- // --- Warehouse setup -------------------------------------------------
await Warehouse.Put("sys", new MemoryStore()); await wh.Put("sys", new MemoryStore());
await Warehouse.Put("sys/server", new DistributedServer() { Port = (ushort)port }); await wh.Put("sys/server", new EpServer() { Port = (ushort)port });
// Create and register all sensor resources // Create and register all sensor resources
var sensors = new SensorResource[resourceCount]; var sensors = new SensorResource[resourceCount];
for (int i = 0; i < resourceCount; i++) for (int i = 0; i < resourceCount; i++)
{ {
sensors[i] = new SensorResource { SensorId = i }; sensors[i] = new SensorResource { SensorId = i };
await Warehouse.Put($"sys/sensor_{i}", sensors[i]); await wh.Put($"sys/sensor_{i}", sensors[i]);
} }
await Warehouse.Open(); await wh.Open();
Console.WriteLine($"[Server] Listening on port {port} with {resourceCount} resources."); Console.WriteLine($"[Server] Listening on port {port} with {resourceCount} resources.");
// --- Emit loop ------------------------------------------------------- // --- Emit loop -------------------------------------------------------
@@ -68,7 +69,7 @@ _ = Task.Run(async () =>
Console.WriteLine("Press ENTER to stop."); Console.WriteLine("Press ENTER to stop.");
Console.ReadLine(); Console.ReadLine();
await Warehouse.Close(); await wh.Close();
// --- Helpers --------------------------------------------------------- // --- Helpers ---------------------------------------------------------
@@ -1,24 +1,15 @@
using Esiur.Resource; using Esiur.Resource;
/// <summary> /// <summary>
/// A simple observable sensor resource. /// Shared observable sensor resource used across all scalability tests.
/// Property changes are automatically propagated to all attached peers. /// Property changes via Value setter are automatically propagated
/// to all attached remote peers by the Esiur runtime.
/// </summary> /// </summary>
[Resource] [Resource]
public class SensorResource : Resource public partial class SensorResource : Resource
{ {
public int SensorId { get; set; } public int SensorId { get; set; }
private double _value; [Export]
public double value;
[ResourceProperty]
public double Value
{
get => _value;
set
{
_value = value;
PropertyModified("Value"); // notifies Esiur runtime to propagate
}
}
} }
@@ -7,4 +7,8 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\Libraries\Esiur\Esiur.csproj" />
</ItemGroup>
</Project> </Project>
@@ -23,7 +23,7 @@ var proxies = new dynamic[resourceCount];
// --- Attach in batches to avoid overwhelming the runtime ------------- // --- Attach in batches to avoid overwhelming the runtime -------------
var totalSw = Stopwatch.StartNew(); var totalSw = Stopwatch.StartNew();
var wh = new Warehouse();
for (int batch = 0; batch < resourceCount; batch += batchSize) for (int batch = 0; batch < resourceCount; batch += batchSize)
{ {
int end = Math.Min(batch + batchSize, resourceCount); int end = Math.Min(batch + batchSize, resourceCount);
@@ -35,7 +35,7 @@ for (int batch = 0; batch < resourceCount; batch += batchSize)
batchTasks[i - batch] = Task.Run(async () => batchTasks[i - batch] = Task.Run(async () =>
{ {
var sw = Stopwatch.StartNew(); var sw = Stopwatch.StartNew();
proxies[capturedI] = await Warehouse.Get<IResource>( proxies[capturedI] = await wh.Get<IResource>(
$"iip://{host}:{port}/sys/sensor_{capturedI}"); $"iip://{host}:{port}/sys/sensor_{capturedI}");
sw.Stop(); sw.Stop();
@@ -7,4 +7,8 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\Libraries\Esiur\Esiur.csproj" OutputItemType="Analyzer"/>
</ItemGroup>
</Project> </Project>
@@ -8,25 +8,27 @@
using Esiur.Resource; using Esiur.Resource;
using Esiur.Stores; using Esiur.Stores;
using Esiur.Net.IIP; using Esiur.Protocol;
var resourceCount = int.Parse(GetArg(args, "--resources", "10000")); var resourceCount = int.Parse(GetArg(args, "--resources", "10000"));
var port = int.Parse(GetArg(args, "--port", "10901")); var port = int.Parse(GetArg(args, "--port", "10901"));
Console.WriteLine($"[Server-T2] Creating {resourceCount} resources on port {port}"); Console.WriteLine($"[Server-T2] Creating {resourceCount} resources on port {port}");
await Warehouse.Put("sys", new MemoryStore()); var wh = new Warehouse();
await Warehouse.Put("sys/server", new DistributedServer() { Port = (ushort)port });
await wh.Put("sys", new MemoryStore());
await wh.Put("sys/server", new EpServer() { Port = (ushort)port });
long memBefore = GC.GetTotalMemory(forceFullCollection: true); long memBefore = GC.GetTotalMemory(forceFullCollection: true);
for (int i = 0; i < resourceCount; i++) for (int i = 0; i < resourceCount; i++)
{ {
var s = new SensorResource { SensorId = i, Value = i * 0.1 }; var s = new SensorResource { SensorId = i, Value = i * 0.1 };
await Warehouse.Put($"sys/sensor_{i}", s); await wh.Put($"sys/sensor_{i}", s);
} }
await Warehouse.Open(); await wh.Open();
long memAfter = GC.GetTotalMemory(forceFullCollection: true); long memAfter = GC.GetTotalMemory(forceFullCollection: true);
double memMB = (memAfter - memBefore) / (1024.0 * 1024.0); double memMB = (memAfter - memBefore) / (1024.0 * 1024.0);
@@ -35,7 +37,7 @@ Console.WriteLine($"[Server-T2] Ready. Resources={resourceCount} MemoryUsed={me
Console.WriteLine($"[Server-T2] Per-resource ≈ {(memAfter - memBefore) / (double)resourceCount:F0} bytes"); Console.WriteLine($"[Server-T2] Per-resource ≈ {(memAfter - memBefore) / (double)resourceCount:F0} bytes");
Console.WriteLine("Press ENTER to stop."); Console.WriteLine("Press ENTER to stop.");
Console.ReadLine(); Console.ReadLine();
await Warehouse.Close(); await wh.Close();
static string GetArg(string[] args, string key, string def) static string GetArg(string[] args, string key, string def)
@@ -0,0 +1,15 @@
using Esiur.Resource;
/// <summary>
/// Shared observable sensor resource used across all scalability tests.
/// Property changes via Value setter are automatically propagated
/// to all attached remote peers by the Esiur runtime.
/// </summary>
[Resource]
public partial class SensorResource : Resource
{
public int SensorId { get; set; }
[Export]
public double value;
}