diff --git a/Lighthouse.sln b/Lighthouse.sln
index f1f9f3cf..6d4ef30b 100644
--- a/Lighthouse.sln
+++ b/Lighthouse.sln
@@ -15,6 +15,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{183986D1
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lighthouse", "src\Lighthouse\Lighthouse.csproj", "{5CA6B9E7-DAF2-47BD-9C91-7E979B4935A1}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lighthouse.Tests.Benchmark", "src\Lighthouse.Tests.Benchmark\Lighthouse.Tests.Benchmark.csproj", "{652331F0-292D-4EDF-B1A0-D8F6DC486DCE}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -29,6 +31,10 @@ Global
{5CA6B9E7-DAF2-47BD-9C91-7E979B4935A1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5CA6B9E7-DAF2-47BD-9C91-7E979B4935A1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5CA6B9E7-DAF2-47BD-9C91-7E979B4935A1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {652331F0-292D-4EDF-B1A0-D8F6DC486DCE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {652331F0-292D-4EDF-B1A0-D8F6DC486DCE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {652331F0-292D-4EDF-B1A0-D8F6DC486DCE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {652331F0-292D-4EDF-B1A0-D8F6DC486DCE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/src/Lighthouse.Tests.Benchmark/Lighthouse.Tests.Benchmark.csproj b/src/Lighthouse.Tests.Benchmark/Lighthouse.Tests.Benchmark.csproj
new file mode 100644
index 00000000..8c1ece88
--- /dev/null
+++ b/src/Lighthouse.Tests.Benchmark/Lighthouse.Tests.Benchmark.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ netcoreapp3.1
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
+
diff --git a/src/Lighthouse.Tests.Benchmark/Program.cs b/src/Lighthouse.Tests.Benchmark/Program.cs
new file mode 100644
index 00000000..87e6e014
--- /dev/null
+++ b/src/Lighthouse.Tests.Benchmark/Program.cs
@@ -0,0 +1,164 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Akka.Actor;
+using Akka.Configuration;
+using Tmds.Utils;
+using Universe.CpuUsage;
+
+namespace Lighthouse.Tests.Benchmark
+{
+ public static class Program
+ {
+ private const int TestLength = 30; // in seconds
+ private const int TestDelay = 5; // in seconds
+ private const int TestRepeat = 20;
+ private const int TestClusterSize = 10;
+
+ private const string ActorSystemName = "CpuTest";
+ private const string LighthouseAddress = "127.0.0.1";
+ private const int LighthousePort = 54053;
+
+ private static readonly List Usages = new List();
+ private static readonly List Processes = new List();
+
+ public static async Task Main(string[] args)
+ {
+ // ExecFunction hook
+ if (ExecFunction.IsExecFunctionCommand(args))
+ return ExecFunction.Program.Main(args);
+
+ // Start lighthouse
+ var service = new LighthouseService(LighthouseAddress, LighthousePort, ActorSystemName);
+ service.Start();
+
+ var executor = new FunctionExecutor(o =>
+ {
+ o.StartInfo.RedirectStandardError = true;
+ o.OnExit = p =>
+ {
+ if (p.ExitCode != 0)
+ {
+ var message =
+ "Function execution failed with exit code: " +
+ $"{p.ExitCode}{Environment.NewLine}{p.StandardError.ReadToEnd()}";
+ throw new Exception(message);
+ }
+ };
+ });
+
+ // Spin up cluster nodes
+ for (var port = LighthousePort + 1; port < LighthousePort + 1 + TestClusterSize; ++port)
+ {
+ Processes.Add(executor.Start(LaunchNode, new []
+ {
+ LighthouseAddress,
+ LighthousePort.ToString(),
+ ActorSystemName,
+ port.ToString()
+ }));
+ }
+
+ // Wait until things settles down
+ await Task.Delay(TimeSpan.FromSeconds(TestDelay));
+
+ // Warm up
+ foreach (var _ in Enumerable.Range(0, 5))
+ {
+ CpuUsage.GetByProcess();
+ }
+
+ // Start benchmark
+ foreach (var i in Enumerable.Range(1, TestRepeat))
+ {
+ var start = CpuUsage.GetByProcess();
+ await Task.Delay(TimeSpan.FromSeconds(TestLength));
+ var end = CpuUsage.GetByProcess();
+ var final = end - start;
+
+ Console.WriteLine($"{i}. Cpu Usage: {final}");
+ Usages.Add(final.Value);
+ }
+
+ // Kill cluster node processes
+ foreach (var process in Processes)
+ {
+ process.Kill();
+ process.Dispose();
+ }
+
+ // Stop lighthouse
+ await service.StopAsync();
+
+ // Generate report
+ var sb = new StringBuilder();
+ sb.AppendLine("CPU Benchmark complete.");
+ sb.AppendLine();
+
+ var num = 1;
+ foreach (var usage in Usages)
+ {
+ sb.AppendLine($"{num++}. CPU usage: user: {usage.UserUsage.TotalMicroSeconds / 1000.0} ms, " +
+ $"kernel: {usage.KernelUsage.TotalMicroSeconds / 1000.0} ms, " +
+ $"total: {usage.TotalMicroSeconds / 1000.0} ms");
+ }
+
+ sb.AppendLine()
+ .AppendLine(" CPU | Mean | StdErr | StdDev | Median |")
+ .AppendLine("---- |----- |------- |------- |------- |")
+ .AppendLine(CalculateResult(Usages
+ .Select(u => u.UserUsage.TotalMicroSeconds)
+ .OrderBy(i => i).ToArray(), "User"))
+ .AppendLine(CalculateResult(Usages
+ .Select(u => u.KernelUsage.TotalMicroSeconds)
+ .OrderBy(i => i).ToArray(), "Kernel"));
+
+ Console.WriteLine(sb.ToString());
+
+ return 0;
+ }
+
+ private static string CalculateResult(long[] times, string name)
+ {
+ var medianIndex = times.Length / 2;
+
+ var mean = times.Average();
+ var stdDev = Math.Sqrt(times.Average(v => Math.Pow(v - mean, 2)));
+ var stdErr = stdDev / Math.Sqrt(times.Length);
+ double median;
+ if (times.Length % 2 == 0)
+ median = (times[medianIndex - 1] + times[medianIndex]) / 2.0;
+ else
+ median = times[medianIndex];
+
+ return $" {name} | {(mean / 1000.0):N3} ms | {(stdErr / 1000.0):N3} ms | {(stdDev / 1000.0):N3} ms | {(median / 1000.0):N3} ms |";
+ }
+
+ // Function will be deployed in separate processes as Main()
+ private static async Task LaunchNode(string[] args)
+ {
+ var seedAddress = args[1];
+ var seedPort = int.Parse(args[2]);
+ var systemName = args[3];
+ var port = int.Parse(args[4]);
+
+ var config = ConfigurationFactory.ParseString($@"
+ akka.actor.provider = cluster
+ akka.cluster.seed-nodes = [""{new Address("akka.tcp", systemName, seedAddress.Trim(), seedPort)}""]
+ akka.remote.dot-netty.tcp {{
+ public-hostname=""{seedAddress}""
+ port={port}
+ }}");
+
+ var system = ActorSystem.Create(systemName, config);
+
+ // wait forever until we get killed
+ await Task.Delay(TimeSpan.FromDays(1));
+
+ return 0;
+ }
+ }
+}
diff --git a/src/Lighthouse.Tests.Benchmark/akka.hocon b/src/Lighthouse.Tests.Benchmark/akka.hocon
new file mode 100644
index 00000000..c5f51514
--- /dev/null
+++ b/src/Lighthouse.Tests.Benchmark/akka.hocon
@@ -0,0 +1,38 @@
+lighthouse{
+ actorsystem: "actor-system" #POPULATE NAME OF YOUR ACTOR SYSTEM HERE
+}
+
+# See petabridge.cmd configuration options here: https://cmd.petabridge.com/articles/install/host-configuration.html
+petabridge.cmd{
+ # default IP address used to listen for incoming petabridge.cmd client connections
+ # should be a safe default as it listens on "all network interfaces".
+ host = "0.0.0.0"
+
+ # default port number used to listen for incoming petabridge.cmd client connections
+ port = 9110
+}
+
+akka {
+ actor {
+ provider = cluster
+ }
+
+ remote {
+ log-remote-lifecycle-events = DEBUG
+ dot-netty.tcp {
+ transport-class = "Akka.Remote.Transport.DotNetty.TcpTransport, Akka.Remote"
+ applied-adapters = []
+ transport-protocol = tcp
+ #will be populated with a dynamic host-name at runtime if left uncommented
+ # public-hostname = "127.0.0.1"
+ # hostname = "0.0.0.0"
+ # port = 4053
+ }
+ }
+
+ cluster {
+ #will inject this node as a self-seed node at run-time
+ seed-nodes = []
+ roles = [lighthouse]
+ }
+}
\ No newline at end of file
diff --git a/src/Lighthouse/LighthouseHostFactory.cs b/src/Lighthouse/LighthouseHostFactory.cs
index c3fb11ad..ddcbc11e 100755
--- a/src/Lighthouse/LighthouseHostFactory.cs
+++ b/src/Lighthouse/LighthouseHostFactory.cs
@@ -24,13 +24,11 @@ public static ActorSystem LaunchLighthouse(string ipAddress = null, int? specifi
{
systemName = systemName ?? Environment.GetEnvironmentVariable("ACTORSYSTEM")?.Trim();
-
- // Set environment variables for use inside Akka.Bootstrap.Docker
- // If overrides were provided to this method.
- //if (!string.IsNullOrEmpty(ipAddress)) Environment.SetEnvironmentVariable("CLUSTER_IP", ipAddress);
-
- //if (specifiedPort != null)
- // Environment.SetEnvironmentVariable("CLUSTER_PORT", specifiedPort.Value.ToString());
+ var argConfig = "";
+ if (ipAddress != null)
+ argConfig += $"akka.remote.dot-netty.tcp.public-hostname = {ipAddress}\n";
+ if (specifiedPort != null)
+ argConfig += $"akka.remote.dot-netty.tcp.port = {specifiedPort}";
var useDocker = !(IsNullOrEmpty(Environment.GetEnvironmentVariable("CLUSTER_IP")?.Trim()) ||
IsNullOrEmpty(Environment.GetEnvironmentVariable("CLUSTER_SEEDS")?.Trim()));
@@ -41,6 +39,11 @@ public static ActorSystem LaunchLighthouse(string ipAddress = null, int? specifi
if (useDocker)
clusterConfig = clusterConfig.BootstrapFromDocker();
+ // Values from method arguments should always win
+ if (!IsNullOrEmpty(argConfig))
+ clusterConfig = ConfigurationFactory.ParseString(argConfig)
+ .WithFallback(clusterConfig);
+
var lighthouseConfig = clusterConfig.GetConfig("lighthouse");
if (lighthouseConfig != null && IsNullOrEmpty(systemName))
systemName = lighthouseConfig.GetString("actorsystem", systemName);