mirror of
https://github.com/esiur/esiur-dotnet.git
synced 2026-04-29 06:48:41 +00:00
dist
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user