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 15:30:06 +03:00
parent 5f73cf7af7
commit 5b0fba89a4
14 changed files with 853 additions and 0 deletions
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
@@ -0,0 +1,114 @@
// ============================================================
// Test 1: Node Fan-Out — CLIENT NODE
// Connects to the server, attaches to all sensor resources,
// and counts received property-change notifications.
//
// Run N instances of this client simultaneously to simulate
// N subscriber nodes. The server's fan-out load grows with N.
//
// Usage: dotnet run -- --host 127.0.0.1 --port 10900 --resources 100 --duration 30
// ============================================================
using Esiur.Resource;
using System.Diagnostics;
var host = GetArg(args, "--host", "127.0.0.1");
var port = int.Parse(GetArg(args, "--port", "10900"));
var resourceCount = int.Parse(GetArg(args, "--resources", "100"));
var durationSec = int.Parse(GetArg(args, "--duration", "30"));
var clientId = GetArg(args, "--id", Environment.MachineName);
Console.WriteLine($"[Client {clientId}] Connecting to {host}:{port}, resources={resourceCount}, duration={durationSec}s");
// Counters
long totalReceived = 0;
long lateCount = 0; // notifications arriving > 500ms after the previous
double sumLatencyMs = 0;
long latencySamples = 0;
var latencyLock = new object();
// --- Attach all resources -------------------------------------------
var proxies = new dynamic[resourceCount];
var sw = Stopwatch.StartNew();
try
{
for (int i = 0; i < resourceCount; i++)
{
proxies[i] = await Warehouse.Get<IResource>($"iip://{host}:{port}/sys/sensor_{i}");
// Subscribe to property change notifications via the Esiur event model
double lastValue = (double)proxies[i].Value;
long lastTick = Stopwatch.GetTimestamp();
int capturedI = i;
proxies[i].OnPropertyModified += (string propName, object oldVal, object newVal) =>
{
if (propName != "Value") return;
long nowTick = Stopwatch.GetTimestamp();
double elapsedMs = (nowTick - lastTick) * 1000.0 / Stopwatch.Frequency;
lastTick = nowTick;
Interlocked.Increment(ref totalReceived);
lock (latencyLock)
{
sumLatencyMs += elapsedMs;
latencySamples++;
if (elapsedMs > 500) lateCount++;
}
};
}
double attachTime = sw.Elapsed.TotalSeconds;
Console.WriteLine($"[Client {clientId}] All {resourceCount} resources attached in {attachTime:F2}s");
}
catch (Exception ex)
{
Console.WriteLine($"[Client {clientId}] Attach error: {ex.Message}");
return;
}
// --- Measurement window ---------------------------------------------
sw.Restart();
long lastReceived = 0;
var results = new List<(double TimeSec, long ReceivedDelta, double AvgIntervalMs)>();
while (sw.Elapsed.TotalSeconds < durationSec)
{
await Task.Delay(5000);
long delta = totalReceived - lastReceived;
lastReceived = totalReceived;
double avgInterval;
lock (latencyLock)
{
avgInterval = latencySamples > 0 ? sumLatencyMs / latencySamples : 0;
sumLatencyMs = 0;
latencySamples = 0;
}
double t = sw.Elapsed.TotalSeconds;
results.Add((t, delta, avgInterval));
Console.WriteLine($"[Client {clientId}] t={t:F0}s recv/5s={delta} rate={delta/5.0:F1}/s avg_interval={avgInterval:F1}ms late={lateCount}");
}
// --- CSV output -----------------------------------------------------
string csv = $"time_s,received_per_5s,rate_per_s,avg_interval_ms\n" +
string.Join("\n", results.Select(r =>
$"{r.TimeSec:F1},{r.ReceivedDelta},{r.ReceivedDelta/5.0:F1},{r.AvgIntervalMs:F2}"));
string outFile = $"client_{clientId}_results.csv";
await File.WriteAllTextAsync(outFile, csv);
Console.WriteLine($"[Client {clientId}] Results written to {outFile}");
Console.WriteLine($"[Client {clientId}] Total received={totalReceived} late(>500ms)={lateCount}");
static string GetArg(string[] args, string key, string def)
{
int i = Array.IndexOf(args, key);
return (i >= 0 && i + 1 < args.Length) ? args[i + 1] : def;
}
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
@@ -0,0 +1,79 @@
// ============================================================
// Test 1: Node Fan-Out — SERVER NODE
// One server hosts N resources. Clients attach and subscribe.
// The server emits property updates at a fixed rate and measures
// notification throughput vs. subscriber count.
// ============================================================
// Usage: dotnet run -- --resources 100 --interval 50
// ============================================================
using Esiur.Resource;
using Esiur.Stores;
using Esiur.Net.IIP;
using System.Diagnostics;
var resourceCount = int.Parse(GetArg(args, "--resources", "100"));
var intervalMs = int.Parse(GetArg(args, "--interval", "50"));
var port = int.Parse(GetArg(args, "--port", "10900"));
Console.WriteLine($"[Server] resources={resourceCount} interval={intervalMs}ms port={port}");
// --- Warehouse setup -------------------------------------------------
await Warehouse.Put("sys", new MemoryStore());
await Warehouse.Put("sys/server", new DistributedServer() { Port = (ushort)port });
// Create and register all sensor resources
var sensors = new SensorResource[resourceCount];
for (int i = 0; i < resourceCount; i++)
{
sensors[i] = new SensorResource { SensorId = i };
await Warehouse.Put($"sys/sensor_{i}", sensors[i]);
}
await Warehouse.Open();
Console.WriteLine($"[Server] Listening on port {port} with {resourceCount} resources.");
// --- Emit loop -------------------------------------------------------
// Continuously update all resource properties at the given interval.
// This drives property-change notifications to all attached clients.
long totalEmitted = 0;
var sw = Stopwatch.StartNew();
_ = Task.Run(async () =>
{
while (true)
{
await Task.Delay(intervalMs);
double value = sw.Elapsed.TotalSeconds;
foreach (var s in sensors)
s.Value = value; // triggers PropertyModified → propagate to peers
totalEmitted += resourceCount;
}
});
// --- Stats reporter --------------------------------------------------
_ = Task.Run(async () =>
{
long lastEmitted = 0;
while (true)
{
await Task.Delay(5000);
long delta = totalEmitted - lastEmitted;
lastEmitted = totalEmitted;
Console.WriteLine($"[Server] {DateTime.Now:HH:mm:ss} emitted/5s={delta} rate={delta/5.0:F0}/s");
}
});
Console.WriteLine("Press ENTER to stop.");
Console.ReadLine();
await Warehouse.Close();
// --- Helpers ---------------------------------------------------------
static string GetArg(string[] args, string key, string def)
{
int i = Array.IndexOf(args, key);
return (i >= 0 && i + 1 < args.Length) ? args[i + 1] : def;
}
@@ -0,0 +1,24 @@
using Esiur.Resource;
/// <summary>
/// A simple observable sensor resource.
/// Property changes are automatically propagated to all attached peers.
/// </summary>
[Resource]
public class SensorResource : Resource
{
public int SensorId { get; set; }
private double _value;
[ResourceProperty]
public double Value
{
get => _value;
set
{
_value = value;
PropertyModified("Value"); // notifies Esiur runtime to propagate
}
}
}